1

I'm trying to set the token attribute on my AuthenticationService.

Here's my code:

authentication.service.ts

import { Injectable } from '@angular/core'
import { Http, Headers, Response } from '@angular/http'
import { Observable, BehaviorSubject } from 'rxjs'
import 'rxjs/add/operator/map'
import { Login } from '../../+authentication/shared/models'
import { config } from '../config'

@Injectable()
export class AuthenticationService {
  private token: string
  private _isConnected = new BehaviorSubject<boolean>(false)
  isConnected$ = this._isConnected.asObservable()

  constructor (private http: Http) {
    const currentToken = JSON.parse(localStorage.getItem('currentToken'))
    if (currentToken) {
      this.token = currentToken.token 
      this._isConnected.next(true) // work
    }
  }

  login (login: Login): Observable<any> {
    return this.http.post(config.BASE_URL + '/auth/login', login)
      .map((response: Response) => {
        if (response.json() && response.json().success) {
          const token = response.json() && response.json().token
          this.token = token// don't do the work
          this._isConnected.next(true) // don't do the work neither

          localStorage.setItem('currentToken', JSON.stringify({token}))

          return {
            success: true
          }
        } else if (response.json() && response.json().message) {
          return {
            success: false,
            message: response.json().message
          }
        }
      })
  }

  logout (): void {
    this.token = null
    this._isConnected.next(false) // work
    localStorage.removeItem('currentToken') // work
  }

  logToken (): void {
    console.log(this.token) // null
    console.log(localStorage.getItem('currentToken')) // {"token":"eyJhb..."}

    // do the work
    this.token = localStorage.getItem('currentToken')
    console.log(this.token)

    // do the work too
    this._isConnected.next(true)
  }
}

Called from login.component.ts

import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'

import { AuthenticationService } from '../../shared/services'

import { Login } from '../shared/models/index'

@Component({
  selector: 'login',
  templateUrl: './login.component.html',
})
export class LoginComponent {
  public login: Login = new Login('test@test.fr', 'test')
  public authError: string

  constructor (
    private router: Router,
    private as: AuthenticationService
  ) {
    if (as.getToken()) {
      this.router.navigate(['/home']) // This work !! That's not 
    }
  }

  onSubmit () {
    this.as.login(this.login)
      .subscribe((result) => {
        if (result.success) {
          this.router.navigate(['/home']) // working
        } else if (result.message) {
          this.authError = result.message // working
        }
      })
  }
}

At the end of authentication.service.ts, you can see the logToken function:

logToken (): void {
    console.log(this.token) // null
    console.log(localStorage.getItem('currentToken')) // {"token":"eyJhb..."}

    // do the work
    this.token = localStorage.getItem('currentToken')
    console.log(this.token)

    // do the work too
    this._isConnected.next(true)
  }

I call it manually from my app component by clicking a button in the navbar, and the token property is never set, unless I reload the page, in that case it's set by the constructor of the service, who gets it from LocalStorage.

Please tell me why the let don't want to work, I used the arrow-function to bind it but it seems it's not working.

Thanks in advance

SherlockStd

EDIT

I tried to delegate the response handling to a private method to see if the this binding is better, but it's still not working (authentication.service.ts):

  login (login: Login): Observable<any> {
    return this.http.post(config.BASE_URL + '/auth/login', login)
      .map((response: Response) => this.handleResponse(response))
  }

  private handleResponse (response: Response) {
    if (response.json() && response.json().success) {
          // login successful if there's a jwt token in the response
      const token = response.json() && response.json().token
      this.token = token

      localStorage.setItem('currentToken', JSON.stringify({token}))

      return {
        success: true
      }
    } else if (response.json() && response.json().message) {
      return {
        success: false,
        message: response.json().message
      }
    }
  }

EDIT 2

I logged the this keyword from authentication.service.ts, inside the login() and logToken() functions. It return an AuthenticationService each times, containing the same attributes but into the login function it contains the token attribute, which is lossed outside this function.

Pierre C.
  • 1,591
  • 2
  • 16
  • 29
  • 1
    Isn't the problem in `const token = response.json() && response.json().token`? I think `&&` makes the expression always a boolean `true/false`. – martin May 07 '17 at 21:22
  • ... The problem is very likely in `this.as.login(this.login)` because there you're losing `this` context, see http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback. Use `this.as.login(data => this.login(data))` instead. – martin May 07 '17 at 21:30
  • @martin why would he be losing the `this` context? `this.as.login(this.login)` is still in the global context at this point? Also won't, `this.login(data)` will just error as `data` will be null at this point? – Zze May 07 '17 at 23:11
  • what do u mean "don't do the work" ? do u see exception or what? do console.log(this) before "don't do the work". what does it print? – Julia Passynkova May 08 '17 at 00:04
  • You should have done at least some debugging yourself. Can you tell us what have done and what is the outcome? 'Don't do the work' is confusing. – wannadream May 08 '17 at 03:17
  • @martin you should provide it as an answer so that OP can mark it as accepted – eko May 08 '17 at 04:24
  • @SherlockStd Did you try the two things I mentioned in the the first comnets? – martin May 08 '17 at 07:50
  • @martin for the const token definition this is definitely not the problem, the response.json() is used to determine if it's undefined to avoid error "Can't read property 'token' of `undefined`" – Pierre C. May 08 '17 at 08:49
  • @martin @Zze is right `this.as.login(data => this.login(data))` can't work, this.login is just a object btw – Pierre C. May 08 '17 at 08:52
  • @JuliaPassynkova Don't do the work mean it doesn't set the `token` attribute on my service, no exception, nothing, but when I try to read the `this.token` after that, it's `undefined` – Pierre C. May 08 '17 at 08:59
  • @all see the edits for more details – Pierre C. May 08 '17 at 10:06

1 Answers1

0

After 3 days of research, I finally found the source of this problem.

I was declaring the same service in the parent (app.module.ts) and the child (authentication.module.ts) modules (see the structure below).

src/
├── app.component.html
├── app.component.ts
├── app.module.ts <== provide AuthenticationService
├── app.routes.ts <== load authentication.module with router.loadChildren()
│
├── +authentication/
│   ├── authentication.component.html
│   ├── authentication.component.ts
│   ├── authentication.module.ts <== provide AuthenticationService
│   ├── authentication.routes.ts <== expose a route to login component
│   └── login
│       ├── login.component.html
│       └── login.component.ts <== using service's methods here don't work
│
└── shared/
    └── services/
        ├── authentication.service.ts
        └── base.service.ts

I can't understand why it influenced the this binding, but it's working now, and I opened an issue on Github because I think an error should throw up in that case.

Thanks for your help.

Pierre C.
  • 1,591
  • 2
  • 16
  • 29