Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 ChangeDetection or Observable?

I'm having difficulty with a component that doesn't refresh upon log in.

The component is navbar.component that contains links to my pages/components. On it, there is a "login" link that renders login.component on which I can provide username and password and click the login button. login.component uses user.service which calls the backend with the username and password provided by login.component, stores the received token and redirects to '/'.

At this point, the "login" link on navbar.component should be hidden and the "logout" link shown, but nothing happens until I click one of the other links on the navbar.

The 2 links are as follows:

<a [hidden]="userService.isLoggedIn" [routerLink]="['Login']">Login</a>
<a [hidden]="!userService.isLoggedIn" (click)="userService.logout();" href="javascript:void(0)">Logout</a>

I understand storing the token and redirecting from user.service doesn't trigger change detection, and that my property userService.isLoggedIn isn't an observable, so I know I'm missing something, but not sure what the approach is/should be.

I have tried injecting ApplicationRef to call the tick() method hoping this would trigger change detection, but failed.

Should I make my property userService.isLoggedIn an observable?

like image 354
Seb Avatar asked May 04 '16 10:05

Seb


2 Answers

export class UserService {
  isLoggedIn:BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
}
@Component({
...
template: `
<a [hidden]="userService.isLoggedIn | async" [routerLink]="['Login']">Login</a>
<a [hidden]="!(userService.isLoggedIn | async)" (click)="userService.logout();" href="javascript:void(0)">Logout</a>
`
})
export class MyComponent {
  constructor(private userService: UserService) {}
}
like image 74
Günter Zöchbauer Avatar answered Sep 26 '22 13:09

Günter Zöchbauer


I understand storing the token and redirecting from user.service doesn't trigger change detection

True, but if you are using the Angular Http service, when data is returned from the service, change detection should run after your callback function/method executes. Is your navbar component using the OnPush change detection strategy? (If so, that would explain why is doesn't update.)

Günter's answer works because the async pipe not only automatically subscribe()s to the observable, but it also calls markForCheck() when a new value is emitted by the observable. This ensures that the component (and all of its ancestors) will be change detected, even if any of them are using OnPush.


An alternative approach, which is a bit more imperative (rather than declarative), that still uses a BehaviorSubject, is as follows:

  • inject ChangeDetectorRef into your navbar component
  • subscribe() to the service observable
  • when the observable fires, call detectChanges(). This will check the component and its children (if there are any). This could be more efficient than using markForCheck(), especially if the NavBar doesn't have any children. But considering that logging in/out are likely rare events, efficiency probably doesn't matter. However, I prefer to use detectChanges() when I'm only changing view/component state (as is the case here, based on how I implemented it below) rather than application state – i.e., something that affects multiple components, hence markForCheck() would then be more appropriate.
@Component({
  ...
  template: `
    <a [hidden]="userIsLoggedIn" [routerLink]="['Login']">Login</a>
    <a [hidden]="!userILoggedIn" (click)="userService.logout()">Logout</a>
  `
})
export class NavBar {
  userIsLoggedIn = false;
  constructor(private userService: UserService, private _ref: ChangeDetectorRef) {}
  ngOnInit() {
     this.userService.isLoggedIn.subscribe( userIsLoggedIn => {
        this.userIsLoggedIn = userIsLoggedIn;
        this._ref.detectChanges();
     });
  }
}
like image 43
Mark Rajcok Avatar answered Sep 22 '22 13:09

Mark Rajcok