angularrxjs

Storing logged in state throughout application to drive UI components


I am trying to create a basic login/logout system in my angular application with basic functions such as:

I have a fastify server which is connected to a mongodb database, this works fine, I can log in and a JWT is returned to the frontend. I am having issues with getting my UI to update when the user is logged in or logged out. For simplicity sake(I will refactor once i figure out this issue), I am driving the UI changes purely based on whether a token is present.

auth/user service:

@Injectable({
  providedIn: 'root',
})
export class UserService {
  router = inject(Router);
  http = inject(HttpClient);

  private tokenSubject = new BehaviorSubject<string | null>(null);
  public token: Observable<string | null> = this.tokenSubject.asObservable();

  constructor() {
    const storedToken = localStorage.getItem('token');
    if (storedToken) {
      this.tokenSubject.next(storedToken);
    }
  }

  public get tokenValue(): string | null {
    return this.tokenSubject.value;
  }

  login(login: Login): Observable<UserToken> {
    return this.http.post<UserToken>(`${environment.apiUrl}/login`, login).pipe(
      map((token) => {
        console.log('token', token);
        localStorage.setItem('token', token.token);
        this.router.navigate(['/test']);
        return token;
      })
    );
  }

  logout(): void {
    localStorage.removeItem('token');
    this.tokenSubject.next(null);

    this.router.navigate(['/login']);
  }

  register(user: SignUp): Observable<string> {
    return this.http.post<string>(`${environment.apiUrl}/register`, user);
  }
}

After I am logged in and a token is returned, I want to hide the login button which is used in my navigation component:

<div class="navbar bg-base-100">
  <div class="flex-1">
    <a class="btn btn-ghost text-xl" routerLink="/">Kilvey League</a>
  </div>
  <div class="flex-none">
    <ul class="menu menu-horizontal px-1">
      @if(!(isLoggedIn$ | async) ){
      <li routerLink="/signup" routerLinkActive="['active']">
        <a>Sign Up</a>
      </li>
      <li routerLink="/login"><a>Login</a></li>
      } @else {
      <li routerLink="/create-match"><a>Create Match</a></li>
      <li (click)="logout()"><a>Logout</a></li>
      }
    </ul>
  </div>
</div>

export class NavigationComponent {
  userService = inject(UserService);
  isLoggedIn$ = this.userService.token;

  async logout() {
    await this.userService.logout();
  }
}

This uses the token from the userservice but after login and routes to /create-match, the login button still persist. If i refresh, login changes to logout but i want it to change in real time.


Solution

  • As per the code you shared, I think the token observable from UserService is not being updated after a successful login.

    Updating it would emit a new value through the token observable and the sign up / login buttons should hide

      login(login: Login): Observable<UserToken> {
        return this.http.post<UserToken>(`${environment.apiUrl}/login`, login).pipe(
          map((token) => {
            console.log('token', token);
            localStorage.setItem('token', token.token);
            this.tokenSubject.next(token); // <- update token observable
            this.router.navigate(['/test']);
            return token;
          })
        );
      }