Angular 5 injecting services into components

I have an issue with my authentication service + component in that the service seems to be reinitialized every time I load up the auth component. The flow that should be present in my app is that the root app-component should, upon the application starting, send a log in request to check if the current session is authenticated or not. This login request is being sent from the auth-service. The auth-service has a subject broadcasting a boolean indicating if the user is authenticated or not, depending on the result of the login/logout operation.

This works great except for one scenario. If I start the app on the auth page, navigate away from the component and go back to it, I cannot get the proper authentication status (true/false) from the service. The field (in the service) when printed states undefined for some reason. To debug, I have even inserted console.logs in the ngOnInit functions to see if any of the components/services were being reinitialized, but nothing.

Here is a code sample of how it looks right now, app.component.ts (root component):

constructor(private requestService: RequestService,
              private authService: AuthService) {}

  ngOnInit() {
    console.log("App component init");
    this.requestService.get('http://localhost:8000/api/csrf/')
      .subscribe(
        success => {
          this.authService.login('', '');
        }
    );
  }

The login request is fired as a result of the first CSRF check, this works well so far.

auth.service.ts

@Injectable()
export class AuthService implements OnInit, OnDestroy {
  authenticated: boolean;
  authSubject: Subject<boolean>;

  constructor(private requestService: RequestService) {
    console.log("Auth service constructor");
    this.authSubject = new Subject<boolean>();
  }

  ngOnInit() {
    console.log("Auth service init");
    this.authSubject.subscribe(
      next => {
        this.authenticated = next;
      }
    );
  }

  login(username: string, password: string) {
    console.log("Auth service login");
    this.requestService.post(LOGIN_URL, { username: username, password: password })
      .subscribe(
        next => {
          this.authSubject.next(true);
          console.log("[AuthService] Success logging in.");
        },
        error => {
          console.log("[AuthService] Error logging in.");
        },
        () => {
          console.log("[AuthService] Auth service completed.");
        }
      );
  }

  logout() {
    this.requestService.post(LOGOUT_URL, {})
    .subscribe(
      next => {
        this.authSubject.next(false);
        console.log('[AuthService] Success logging out.');
      },
      error => {
        console.log("[AuthService] Error logging out.");
      },
      () => {
        console.log("[AuthService] Auth service completed.");
      });
  }

  isAuthenticated(): boolean {
    return this.authenticated;
  }

  ngOnDestroy() {
    console.log("Auth service destroyed");
    this.authSubject.unsubscribe();
  }
}

Here we go, as you can see above, I have resorted to instantiating the Subject in the constructor rather than in ngOnInit. This is because when the login is fired from app.component.ts the subject has not yet been created which causes a crash. This still works though.

auth.component.ts

export class AuthComponent implements OnInit {
  authenticated: boolean;

  constructor(private authService: AuthService) { }

  ngOnInit() {
    console.log("Auth component init");
    this.authService.authSubject.subscribe(
      next => {
        this.authenticated = next;
      }
    );
    this.authenticated = this.authService.isAuthenticated();
    console.log(this.authenticated);
  }

  onLogin(form: NgForm) {
    const username = form.value.username;
    const password = form.value.password;
    this.authService.login(username, password);
  }

  onLogout() {
    this.authService.logout();
  }

So, here is where I am stuck. When I login, see that I successfully get a response and that authenticated = true. However, when I navigate away from the auth view and then back to it, getting the authenticated value from authService.isAuthenticated gives me back "undefined"! The service is there and intact (I tapped into the ngOnDestroy for the service, nothing is fired there) so I am guessing there is a reference problem or something, I just can't find anything in the documentation to help me out.

Please advise.

2 answers

  • answered 2018-03-13 20:17 Daniel W Strimpel

    Try to use a BehaviorSubject instead of just a Subject. A BehaviorSubject will broadcast out the last value before subscription plus any new values whereas a subject will only broadcast out any new data after you subscribe.

    What is the difference between Subject and BehaviorSubject?

  • answered 2018-03-13 20:17 Måns Thörnvik

    The problem lied in that ngOnInit is not called for angular services, so the subscription was never activated in the auth service. When I moved the subscription to the services constructor it all works!