Search code examples
angularfirebasegoogle-cloud-platformangularfire

AngularFire auth, template not updated when Observable<User> changes


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?


Solution

  • 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)
    );