Search code examples
angularauthenticationnavbarbehaviorsubject

Notify navbar of authenticated status when logged in (Angular)


I'm using an nx monorepo (Angular/NestJS), and when I log in (using the login component), I want the navbar (in a different lib) to print out the username, and conditionally, the Log out button. In the auth.service.ts, I created 2 BehaviourSubjects, which I manipulate, and 2 public observables, on which I can subscribe to, in other components (like navbar):

 private isAuthenticatedSubject = new BehaviorSubject<boolean>(
    this.hasToken()
  );

  private currentUserSubject = new BehaviorSubject<{
    username: string;
    roles: string[];
  }>(this.getUserFromToken());

  isAuthenticated$: Observable<boolean> =
    this.isAuthenticatedSubject.asObservable();
  currentUser$: Observable<{ username: string; roles: string[] }> =
    this.currentUserSubject.asObservable();

And when the login happens, I notify these 2 private BehaviorSubjects about it:

  public login(username: string, password: string): Observable<any> {
    return this.http.post(`${this.apiURL}/login`, { username, password }).pipe(
      tap((response: any) => {
        if (response.token) {
          this.onLoginHandle(response.token);
        }
      })
    );
  }

  public onLoginHandle(token: string) {
    localStorage.setItem('token', token);
    this.isAuthenticatedSubject.next(true);

    of(null)
      .pipe(
        delay(0), // Small delay to ensure token is set
        tap(() => {
          const user = this.getUserFromToken();
          this.currentUserSubject.next(user);
          this.router.navigate(['../']);
        })
      )
      .subscribe();
  }
private getUserFromToken() {
    const token = localStorage.getItem('token');
    if (token) {
      const decodedToken: any = jwt_decode.jwtDecode(token);
      return {
        username: decodedToken.user,
        roles: decodedToken.roles || [],
      };
    } else {
      return {
        username: '',
        roles: [],
      };
    }
  }

The login method is, what I call, when I submit the login form. Now in the navbar, I created 2 properties (username, to print the logged in user's username, and isLoggedIn, which handles the *ngIf directive on the 'Logout' button). I subscribed both on the isAuthenticated$ and the currentUser$ BehaviourSubject:

  public username: string;
  public isLoggedIn: boolean = false;

  constructor(
    public authService: AuthService,
    private cdr: ChangeDetectorRef
  ) {}

  ngOnInit(): void {
    this.isAuthenticatedSub = this.authService.isAuthenticated$.subscribe(
      (isAuthenticated) => {
        this.isLoggedIn = isAuthenticated;
      }
    );

    this.currentUsersub = this.authService.currentUser$.subscribe({
      next: (user) => {
        console.log('Received user data in navbar:', user);
        this.username = user.username;
        this.cdr.detectChanges();
      },
      error: (error: HttpErrorResponse): void => {
        console.error('Error receiving user data:', error);
      },
    });
  }

But after the login is successful, and the app navigates to the homepage, the navbar (which is there from the start) still does not show the username, nor the Logout button. But if I refresh the page, they are there. Why does the subscription to these 2 auth properties does not behave the way I want? Did i miss something? The console.log('Received user data in navbar:', user); runs only at page reload, though i think, it should run every time, the value of the currentUser$ changes (like, right at login).


Solution

  • Since the navbar is its own library, it mostly has a different instance of authService so the subject it not receiving the event. So you can create a new service within the navbar library called NavbarService which can have providedIn: 'root' so that you can send events through this singleton service from your main application.