Reputation: 9229
I have a simple Angular2 based web app, with authentication. I am running into an issue, where the navigation bar looks different based on whether the user is logged in or not, and also some components won't display when the user isn't logged in.
The issue is, for example, when a user clicks logout
, the navbar changes, but the other component doesn't disappear until the page is refreshed. How can I trigger a component refresh when the LogOut button is pressed?
//user.service.ts
logout() {
localStorage.removeItem('id_token');
this.loggedIn = false;
}
//nav.component.ts
import { Component, Inject } from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
import { UserService } from '../user/services/user.service';
@Component({
selector: 'nav-bar',
template: `
<div class="nav">
<a [routerLink]="['LoginComponent']" *ngIf="!_userService.isLoggedIn()">Login</a>
<a [routerLink]="['SignupComponent']" *ngIf="!_userService.isLoggedIn()">Sign Up</a>
<a [routerLink]="['TodoComponent']" *ngIf="_userService.isLoggedIn()">ToDo</a>
<button (click)="_userService.logout($event)" *ngIf="_userService.isLoggedIn()">Log Out</button>
</div>
`,
styleUrls: ['client/dev/todo/styles/todo.css'],
directives: [ROUTER_DIRECTIVES],
providers: [ UserService ]
})
export class NavComponent {
constructor(@Inject(UserService) private _userService: UserService) {}
}
The nav component renders above whatever the router generates.
How can I trigger components to reset?
Upvotes: 5
Views: 8933
Reputation: 179
It is necessary (I think) because Angular 2 no longer has two-way-binding
.
In my LoginService I use ReplaySubject (it is similar to the Observable object, but 'stores' the last value and 'replays' it whenever it's subscribed to) from RxJS, then I subscribe to that ReplaySubject from the components that I want to update.
Example Login Service
import {ReplaySubject, Observable} from 'rxjs/Rx';
import {Injectable} from 'angular2/core';
@Injectable()
export class LoginService {
private logged = new ReplaySubject<boolean>(1); // Resend 1 old value to new subscribers
// isLoggedIn checks with server if user is already logged in.
// Returns true if logged in, and caches the result and user data.
isLoggedIn(): ReplaySubject<boolean> {
return this.logged;
}
// logIn attempts to log in a user, returns attempt result.
// Caches login status.
logIn(user: string, password: string): Observable<boolean> {
return Observable.timer(100).map(() => { // emulate a 100ms delay on login
this.logged.next(true);
return true;
})
}
logOut() {
// need api call to logout here
this.user = null;
this.logged.next(false);
}
}
Example Component
import {Component} from 'angular2/core';
import {LoginService} from "../user/login.service";
@Component({
selector: 'an-menu',
templateUrl: 'app/user/menu.html',
})
export class MenuComponent {
loggedIn: boolean;
constructor(private _loginService: LoginService) {
this._loginService.isLoggedIn()
.subscribe(r => {
this.loggedIn = r;
});
}
onLogout() {
this._loginService.logOut();
}
}
This way you can use the loggedIn
variable in your template, you can also subscribe to the the ReplaySubject
from different components.
Upvotes: 6
Reputation: 3175
You have a few options. The easiest is to trigger a router
event that will cause angular to re-evaluate your bindings. This is probably not what you want because you will have to direct the router to a completely different "page". Typically in this scenario I use a static login page that always redirects back into the APP instead of building the login logic into the APP itself. This seems to work better in the long run almost all the time.
But we can get your components to update when that logout event is fired by looking at what that is doing exactly.
logout() {
localStorage.removeItem('id_token');
this.loggedIn = false;
}
As you can see this is setting the loggedIn
property on that Model to false. This property is "observed" by the APP (Angular). When it is updated it updates your nav bar due to the following directive in your nav-bar template
*ngIf="!_userService.isLoggedIn()
That directive says to Angular; "If this property is FALSE render this content". As it is included in your @Component template it is rendered and evaluated by Angular upon rendering, but will continue to receive updates from Angular when that property changes.
This is what you will want to do for every component that will need this logic....
"But that is alot of work and tracking"
And you would be right in that statement. I would define common sets of these Components and then include the Components as sets at a higher level so you can easily do the updates at a single point in a top-level component. The top-level component would then setup a 'DIV' in which those component are templating in under the same ngIf
directive.
Upvotes: 2