Home > Back-end >  Angular Behaviour Subject does not reflect change status in a component
Angular Behaviour Subject does not reflect change status in a component

Time:05-19

I have a MEAN project with Angular Behaviour Subject emitting the status of logged in user.

Problem: When user logs in, app component detects the change in user status. But the header component(which is part of app component) does not detect the same. Therefore although the dashboard component is loaded upon login, the menu and logout options are not displayed in the header(header Component). However, if the screen is refreshed then the user account status is detected in header and the menu is displayed. What am I missing here?

app.component.ts

export class AppComponent implements OnInit, OnDestroy {
  isLoading = false;
  account: Account;
  accountSub: Subscription;
  
  constructor(
    private adminService: AdminService,
    private accountService: AccountService) {
      this.accountSub = this.accountService.account.subscribe(x => {
        this.account = x;
        this.isLoading = false
      });
    }

}

app.component.html

<main style="width: 100%">
  <app-header></app-header>  //THIS DOESNOT GET updated immly upon logging in

  <div style="margin-left:auto; margin-right:2%" *ngIf="account"> // THIS DOES GET updated immly upon logging in
      <mat-icon class='link' [routerLink]="['/']">speed</mat-icon> &nbsp; &nbsp;
      <mat-icon class='link' [routerLink]="['/list']">list</mat-icon>
  </div>

  <div  *ngIf="isLoading">
    <mat-progress-bar mode="indeterminate" color="accent"></mat-progress-bar>
  </div>

  <router-outlet></router-outlet>
  <br>
</main>

header.component.ts

export class HeaderComponent implements OnInit {
  account: Account;

  constructor(
    public accountService: AccountService,
    private fb: FormBuilder,
    private router: Router
    ) 
  {
    this.accountService.account.subscribe(x => {
      this.account = x;
      this.isLoading = false
    });
  }

header.component.html: This does not get updated immediately after logging in. i.e. the menu, employee name and logout option does not get shown. But, it does get updated if the page is refreshed.

<div >
    <mat-icon *ngIf="account"  [matMenuTriggerFor]="menu">menu</mat-icon>
    <span >My Project</span>

    <span *ngIf="account" >{{ account.employeeName }}</span>
    <span *ngIf="account"  (click)="onLogout()">Logout</span>

    <span *ngIf="!account"  routerLink="/auth/login" style="margin-left: auto">
      Login
    </span>
</div>

account.service.ts: This is where the Behaviour Subject for account info is defined and emitted.

@Injectable({ providedIn: 'root' })
export class AccountService {
  private accountSubject: BehaviorSubject<Account>;
  public account: Observable<Account>;
  reqUrl: string = environment.apiUrl   '/auth'

  constructor(private httpClient: HttpClient, private router: Router) {
    this.accountSubject = new BehaviorSubject<Account>(null);
    this.account = this.accountSubject.asObservable();
  }

  public get accountValue(): Account {
    return this.accountSubject.value;
  }

  login(userName: string, password: string) {
    return this.httpClient.post<any>(`${this.reqUrl}/authenticate`, {userName, password}, { withCredentials: true })
      .pipe(map(account => {
        if(account.message) {
          return account.message;
        } else {
          // console.log('piping account', account)
          this.accountSubject.next(account);  ///// EMITTING account upon logging in
          this.startRefreshTokenTimer();
          return account;
        }
      }));
  }

CodePudding user response:

write your subscription code in ngOnInit. hope it will work.

ngOnInit(): void {
   this.accountSub = this.accountService.account.subscribe(x => {
    this.account = x;
    this.isLoading = false
  });
}

CodePudding user response:

Assuming http call actually returns something and there is something to be nexted, then if it is an observable that you have created yourself, it may well be that its value changed before you subscribe to it.

But what I don't understand is that why are you making accountSubject type of BehaviourSubject but then defining account that is accountSubject as Observable.

Also function login() should return something, no? Maybe an Observable<Account> so returns inside {} will have no effect.

@Injectable({ providedIn: 'root' })
export class AccountService {
public account: BehaviorSubject<Account>  = new BehaviorSubject<Account>(null);  
reqUrl: string = environment.apiUrl   '/auth'

  constructor(private httpClient: HttpClient, private router: Router) {}

  public get accountValue(): Account {
    return this.account.value;
  }

  login(userName: string, password: string): Observable<Account> {
    return this.httpClient.post<any>(`${this.reqUrl}/authenticate`, {userName, password}, { withCredentials: true })
      .pipe(map(account => {
          this.account.next(account);
          this.startRefreshTokenTimer();   
      }));
  }

Don't forget to unsubscribe when using manual subscriptions! I would reccomend | async pipe to so there's no need to even do that.

export class HeaderComponent implements OnInit {
  account$: Observable<Account> = this.accountService.account;

  constructor(
    public accountService: AccountService,
    private fb: FormBuilder,
    private router: Router
    ) 
  {}

Html

<div >
    <mat-icon *ngIf="account$ | async"  [matMenuTriggerFor]="menu">menu</mat-icon>
    <span >My Project</span>

    <span *ngIf="account$ | async" >{{ account.employeeName }}</span>
    <span *ngIf="account$ | async"  (click)="onLogout()">Logout</span>

    <span *ngIf="!account$ | async"  routerLink="/auth/login" style="margin-left: auto">
      Login
    </span>
</div>
  • Related