I am a casual user of Angular so maybe I'm missing something obvious.
I followed this lesson from fireship.io to integrate authentication in my angular app using angularfire.
After signing in, the observable AuthService.user$ changes, but the template doesn't update in the UserProfile component.
Using the code snippet below confirms that the data is there.
<pre>{{auth.user$ | async | json}}</pre>
This seems to have something to do with the observable being updated outside of the ngzone. I tried to manually detect changes by injecting ChangeDetectorRef in the component and triggering detectChange from a subscribe callback of the AuthService.user$, without success.
I only managed to make it work as expected by changing user-profile.component.ts to the following:
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit {
userData?: User;
constructor(public auth: AuthService) {
this.auth.user$.subscribe(d => this.userData = d)
}
}
And user-profile.component.html to the following:
<div *ngIf="userData; then authenticated else guest">
</div>
<ng-template #guest>
<h3>Hello</h3>
<p>Login to get started...</p>
<button (click)="auth.googleLogin()">
<i class="fa fa-google"></i> Connect Google
</button>
</ng-template>
<ng-template #authenticated>
<div *ngIf="userData as user">
<h3>Hello, {{ user.displayName }}</h3>
<img [src]="user.photoURL">
<button (click)="auth.signOut()">Logout</button>
</div>
</ng-template>
Here are my dependencies, extracted from package.json.
{
"@angular/animations": "~8.2.14",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",
"@angular/core": "~8.2.14",
"@angular/fire": "^5.4.0",
"@angular/forms": "~8.2.14",
"@angular/platform-browser": "~8.2.14",
"@angular/platform-browser-dynamic": "~8.2.14",
"@angular/router": "~8.2.14",
"firebase": "^7.8.0",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
}
Any idea of what I might be missing?
Unless I'm mistaken, you're changing a little the code of Jeff Delaney's example.
You are using two ng-template
and also two async
subscription on your AuthService.user$
, (one in the div
container, and also one in the ng-template
called authenticated
. And this is the cause of your issue.
With this code below, it will work smoothly:
<div *ngIf="auth.user$ | async as user; else guest">
<h3>Hello, {{ user.displayName }}</h3>
<img [src]="user.photoURL">
<button (click)="auth.signOut()">Logout</button>
</div>
<ng-template #guest>
...
</ng-template>
If you want to keep your initial code, you can consider adding shareReplay
operator to your AuthService.user$
observable. In this case, the last value of Observable
will always be available to new subscription.
this.user$ = this.afAuth.authState.pipe(
switchMap(user => {
if (user) {
return this.afs.doc<User>(`users/${user.uid}`).valueChanges();
} else {
return of(null);
}
}),
shareReplay(1)
);