0
votes

I'm trying to hide the login item from the navbar menu in my Angular 6 app. I have an AuthService like below:

import { Injectable, Inject } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { JwtHelperService } from '@auth0/angular-jwt';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private url: string;
  private loggedIn = new BehaviorSubject<boolean>(false);
  isLoggedIn$ = this.loggedIn.asObservable();

  constructor(private http: HttpClient, private jwtHelper: JwtHelperService, @Inject('BASE_URL') baseUrl: string) {
    this.url = baseUrl + 'api/auth';
    this.loggedIn.next(this.isLoggedIn());
  }

  login(credentials) {
    const headers = new HttpHeaders({ 'Content-Type': 'application/json' });

    return this.http.post<any>(`${this.url}/login`, JSON.stringify(credentials), { headers: headers }).pipe(
      map(response => {
        if (response && response.token) {
          localStorage.setItem('token', response.token);
          this.loggedIn.next(true);

          return true;
        }        

        return false;
      })
    );
  }

  logout() {
    localStorage.removeItem('token');
    this.loggedIn.next(false);
  }

  isLoggedIn() {
    return !this.jwtHelper.isTokenExpired();
  }
}

Here's what I have in my NavMenuComponent:

export class NavMenuComponent implements OnInit, OnDestroy {
  isExpanded = false;
  isLoggedIn: boolean;
  authSubscription: Subscription;

  constructor(private router: Router, private authService: AuthService) {}

  ngOnInit() {
    this.authSubscription = this.authService.isLoggedIn$
      .subscribe(loggedIn => this.isLoggedIn = loggedIn);
  }

  ngOnDestroy() {
    this.authSubscription.unsubscribe();
  }

  onLogout() {
    this.authService.logout();
    this.router.navigate(['/login']);
  }

  collapse() {
    this.isExpanded = false;
  }

  toggle() {
    this.isExpanded = !this.isExpanded;
  }
}

And, here's the html:

<li *ngIf="!isLoggedIn" class="nav-item" [routerLinkActive]="['link-active']">
  <a class="nav-link" [routerLink]="['/login']">Login</a>
</li>

But, when I run this, I get the following error:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: true'. Current value: 'ngIf: false'.

I did read about this error, but I didn't understand how I can resolve it. Any help?

3
Unfortunately, html didn't make it to the post, please update it. - Alexander Leonov
@AlexanderLeonov Sorry! Updated. - ataravati
This error will only appear in your dev environment. If you enable prod mode which you will when you deploy the code, it will go away. On a side note, if you use async pipe directly, you won't have to manage states like isLoggedIn and angular will take care of unsunscribing as well. So you won't need to do this.authSubscription.unsubscribe(); as well - Vatsal

3 Answers

0
votes

My guess would be that it has to do with the way you set the initial value for loggedIn in your service constructor: this.loggedIn.next(this.isLoggedIn());

Try to only declare the variable in your class, and only make it a new BehaviourSubject() in your contructor.

Something like:

 private url: string;
  private loggedIn: BehaviorSubject<boolean>;
  isLoggedIn$: Observable<boolean>;

  constructor(private http: HttpClient, private jwtHelper: JwtHelperService, @Inject('BASE_URL') baseUrl: string) {
    this.url = baseUrl + 'api/auth';
    this.loggedIn = new BehaviourSubject<boolean>(this.isLoggedIn());
    this.isLoggedIn$ = this.loggedIn.asObservable()
  }

Let me know if that changes anything

0
votes

You try to use a method instead of a property to return a observable on AuthService

isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject(false);

isLoggedIn$() {
    return this.isLoggedIn.asObservable();
}
0
votes

you may resolve this error with setTimeout.

  export class NavMenuComponent implements OnInit, OnDestroy {
  isExpanded = false;
  isLoggedIn: boolean;
  authSubscription: Subscription;

  constructor(private router: Router, private authService: AuthService) {}

  ngOnInit() {
   setTimeout(() => {
    this.authSubscription = this.authService.isLoggedIn$
      .subscribe(loggedIn => this.isLoggedIn = loggedIn);
   });
  }

  ngOnDestroy() {
    this.authSubscription.unsubscribe();
  }

  onLogout() {
    this.authService.logout();
    this.router.navigate(['/login']);
  }

  collapse() {
    this.isExpanded = false;
  }

  toggle() {
    this.isExpanded = !this.isExpanded;
  }
}