'Angular Behaviour Subject does not reflect change status in a component
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>
<mat-icon class='link' [routerLink]="['/list']">list</mat-icon>
</div>
<div class="spinner-container" *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 class="app-toolbar">
<mat-icon *ngIf="account" class="menu-icon" [matMenuTriggerFor]="menu">menu</mat-icon>
<span class="header-title">My Project</span>
<span *ngIf="account" class="header-username">{{ account.employeeName }}</span>
<span *ngIf="account" class="header-menu-link" (click)="onLogout()">Logout</span>
<span *ngIf="!account" class="header-menu-link" 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;
}
}));
}
Solution 1:[1]
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
});
}
Solution 2:[2]
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 class="app-toolbar">
<mat-icon *ngIf="account$ | async" class="menu-icon" [matMenuTriggerFor]="menu">menu</mat-icon>
<span class="header-title">My Project</span>
<span *ngIf="account$ | async" class="header-username">{{ account.employeeName }}</span>
<span *ngIf="account$ | async" class="header-menu-link" (click)="onLogout()">Logout</span>
<span *ngIf="!account$ | async" class="header-menu-link" routerLink="/auth/login" style="margin-left: auto">
Login
</span>
</div>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Waqar Ahmad |
| Solution 2 |
