Angular2 ChangeDetection or Observable?
Asked Answered
F

2

6

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?

Foredate answered 4/5, 2016 at 10:21 Comment(0)
D
7
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) {}
}
Drysalter answered 4/5, 2016 at 10:26 Comment(4)
Thanks but I get an error: Invalid argument 'false' for pipe 'AsyncPipe' in [!userService.isLoggedIn | async in NavbarComponent@4:5] :(Foredate
It works, thanks alot! I set the new value of isLoggedIn like so: this.isLoggedIn.next(true);Foredate
Just a quick question, what's a BehaviorSubject? Is it like an observable for basic types?Foredate
Subject and BehaviorSubject are easy ways to create an Observable where BehaviorSubject provides the last emitted value immediately for new subscribers. Otherwise if one subscribes to a subject/observable there won't be any value until some new value is emitted after the subscription was added. The false passed to new BehaviorSubject() is the initial value all subscribers get until another value was emitted with isLoggedIn.emit(someValue)Underwater
E
3

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();
     });
  }
}
Edelstein answered 9/5, 2016 at 17:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.