Reputation: 836
I have an Angular (5.2.3) app where I want to display a login/logout button in the upper right corner of the page. Behind the scenes, I try to log the user in silently using an external Open ID Connect provider. When the callback arrives, I want to display the user's name and a logout button. But alas, the view is never updated to reflect this.
Here's the component's view:
<ul class="navbar-nav">
<li class="nav-item" *ngIf="!loggedIn">
<a href="#" class="nav-link" (click)="login()">Login</a>
</li>
<li class="nav-item" *ngIf="loggedIn">
<a href="#" class="nav-link disabled" disabled>Hello, {{ name }}</a>
</li>
<li class="nav-item" *ngIf="loggedIn">
<a href="#" class="nav-link" (click)="logoff()">Logout</a>
</li>
</ul>
import {
Component,
OnInit,
SimpleChanges,
ApplicationRef,
ChangeDetectorRef,
NgZone
} from '@angular/core';
import {
OAuthService
} from 'angular-oauth2-oidc';
import {
SessionService
} from '../session.service';
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
name: string;
loggedIn: boolean;
constructor(private oauthService: OAuthService,
private sessionService: SessionService,
private applicationRef: ApplicationRef,
private zone: NgZone,
private cd: ChangeDetectorRef) {
//this.loggedIn = false;
}
ngOnInit() {
this.sessionService.state.subscribe(isLoggedIn => {
console.log('Detected a state change! User is logged in: ' + isLoggedIn);
this.zone.run(() => {
if (!isLoggedIn) {
this.name = null;
this.loggedIn = false;
console.log('User not logged in. Name is set to null');
} else {
const claims: any = this.oauthService.getIdentityClaims();
console.log(`Got claims. Name is now ${claims.name}`);
this.name = claims.name;
this.loggedIn = true;
}
});
this.cd.detectChanges();
this.applicationRef.tick();
});
this.sessionService.configureWithNewConfigApi();
}
public login() {}
public logoff() {}
}
console.log
calls are executed as expected, only the view is never updated.
Upvotes: 4
Views: 1058
Reputation: 12342
Your problem is similar to another one, on which I placed answer in the past.
Trigger update of component view from service - No Provider for ChangeDetectorRef
What you need to do is force Angular to update your view. You can do that with ApplicationRef
. This is how your service could look like:
import {Injectable, ApplicationRef } from '@angular/core';
@Injectable()
export class UserService {
private isLoggedIn: boolean = false;
constructor(private ref: ApplicationRef) {}
checkLogin() {
let stillLogged = //some logic making API calls, or anything other.
if (this.isLoggedIn != stillLogged) {
this.isLoggedIn = stillLogged;
this.ref.tick(); //trigger Angular to update view.
}
}
}
In this documentation page you can find out more about ApplicationRef
You could try also move this.applicationRef.tick();
inside this.zone.run(()=>{});
(if you really need NgZone
).
Upvotes: 1
Reputation: 360
You can run the code inside the ngZone so as to make the view updated according to the logged in status. Follow as mentioned below:
this.ngZone.run(() => {
this.loggedIn = true;
});
So the code becomes as follows:
import {
Component,
OnInit,
SimpleChanges,
ApplicationRef,
ChangeDetectorRef,
NgZone } from '@angular/core';
import { OAuthService} from 'angular-oauth2-oidc';
import { SessionService } from '../session.service';
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
name: string;
loggedIn: boolean;
constructor(private oauthService: OAuthService,
private sessionService: SessionService,
private zone: NgZone) {
}
ngOnInit() {
this.sessionService.state.subscribe(isLoggedIn => {
console.log('Detected a state change! User is logged in: ' + isLoggedIn);
if (!isLoggedIn) {
this.name = null;
this.loggedIn = false;
console.log('User not logged in. Name is set to null');
} else {
const claims: any = this.oauthService.getIdentityClaims();
console.log(`Got claims. Name is now ${claims.name}`);
this.name = claims.name;
this.zone.run(() => {
this.loggedIn = true;
});
}
});
this.sessionService.configureWithNewConfigApi();
}
public login() {}
public logoff() {}
}
I hope this can solve your issue regarding the view updation.
Upvotes: 0
Reputation: 886
I can't seem to find any issues with the Code that your provided here. All i can think of is some kind of conflict with the ngIf directive.
If all the console logs are as expected, all i can suggest is that you initialize the loggedIn
value at the start and then try using an *ngIF else
template in your Html file.
loggedIn: boolean = false;
And the html is this:
<ul class="navbar-nav">
<li class="nav-item" *ngIf="!loggedIn; else loggedInTemplate">
<a href="#" class="nav-link" (click)="login()">Login</a>
</li>
<ng-template #loggedInTemplate>
<li class="nav-item">
<a href="#" class="nav-link disabled" disabled>Hello, {{ name }}</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" (click)="logoff()">Logout</a>
</li>
</ng-template>
</ul>
Other than that, there isn't something i can actually detect here. Also i can't simulate the error because there is no code provided for the sessionService. Hope i helped.
Upvotes: 0
Reputation: 2629
I think your error is to put all you have seen on the internet in order to detect changes etc and maybe something weird is happening. Can you just try this? It should work using only the detectChanges() method. It may not be the best solution, but it should work...
Better approach, if the subscribe() is inside the zone, will be putting a markForCheck()... but as I said, detectchanges alone should work.
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
name: string;
loggedIn: boolean;
constructor(private oauthService: OAuthService,
private sessionService: SessionService,
private applicationRef: ApplicationRef,
private zone: NgZone,
private cd: ChangeDetectorRef) {
//this.loggedIn = false;
}
ngOnInit() {
this.sessionService.state.subscribe(isLoggedIn => {
console.log('Detected a state change! User is logged in: ' + isLoggedIn);
if (!isLoggedIn) {
this.name = null;
this.loggedIn = false;
console.log('User not logged in. Name is set to null');
} else {
const claims: any = this.oauthService.getIdentityClaims();
console.log(`Got claims. Name is now ${claims.name}`);
this.name = claims.name;
this.loggedIn = true;
}
this.cd.detectChanges();
});
this.sessionService.configureWithNewConfigApi();
}
public login() {}
public logoff() {}
}
Hope this helps.
Upvotes: 0
Reputation: 2274
This is my suggestion about I'll do it using observables.
In my service:
export class MyLoginService {
loginSubject: ReplaySubject<any>;
login$: Observable<any>;
constructor(private http: HttpClient) {
this.loginSubject = new ReplaySubject<any>();
this.login$ = this.loginSubject.asObservable();
}
login(): void {
// do your login logic and once is done successfully
this.loginSubject.next({loggedIn: true});
}
}
In my html:
<ul class="navbar-nav">
<li class="nav-item" *ngIf="!(myLoginSrv.login$ | async)?.loggedIn">
<a href="#" class="nav-link" (click)="login()">Login</a>
</li>
<li class="nav-item" *ngIf="(myLoginSrv.login$ | async)?.loggedIn">
<a href="#" class="nav-link disabled" disabled>Hello, {{ name }}</a>
</li>
<li class="nav-item" *ngIf="(myLoginSrv.login$ | async)?.loggedIn">
<a href="#" class="nav-link" (click)="logoff()">Logout</a>
</li>
</ul>
In my component:
@Component({
...
})
export class MyComponent {
constructor(public myLoginSrv: MyLoginService) {}
...
}
Upvotes: 0