AngularFire2谷歌登录组件获取用户数据返回undefined问题排查
Hey there, let's break down why you're seeing undefined when calling getUserDetails():
The core issue here is asynchronous behavior. Your UserService initializes userData inside the subscription to afAuth.authState—this subscription runs asynchronously. When your AppComponent calls getUserDetails() in ngOnInit(), that subscription hasn't had time to fire yet, so userData is still its initial undefined value.
Firebase's auth state takes a moment to resolve (whether it's loading from local storage or verifying with the server), and your synchronous call to getUserDetails() happens before that process finishes.
Here are two solid ways to fix this:
1. Use Observables directly (recommended for Angular)
Instead of relying on a synchronous userData property, let your component subscribe to the auth state observable directly. This ensures you always get the latest user data as soon as it's available.
Update UserService:
import { Injectable } from '@angular/core'; import { AngularFireAuth } from 'angularfire2/auth'; import * as firebase from 'firebase'; import { Observable } from 'rxjs/Observable'; @Injectable() export class UserService { // Keep the auth state observable exposed user$: Observable<firebase.User | null>; constructor(public afAuth:AngularFireAuth) { this.user$ = this.afAuth.authState; // You can still subscribe here for service-internal logic this.user$.subscribe((user)=> { if(user){ console.log(user.displayName); } }); } signInWithGmail(){ this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()); } signOut(){ this.afAuth.auth.signOut(); } // No need for getUserDetails anymore, just use user$ directly }
Update AppComponent:
import { Component } from '@angular/core'; import { UserService } from './user.service' import { OnInit } from '@angular/core/src/metadata/lifecycle_hooks'; import * as firebase from 'firebase'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit{ userDetail: firebase.User | null; constructor(public userService: UserService){} ngOnInit(){ // Subscribe to the user observable to get real-time updates this.userService.user$.subscribe(user => { this.userDetail = user; console.log('User details:', user); // This will log the user once auth state resolves }); } onSubmit(){ this.userService.signInWithGmail(); } onLogOut(){ this.userService.signOut(); console.log("Logged Out!"); } }
2. Use a BehaviorSubject to cache user state
If you prefer to maintain a cached user object in your service, use a BehaviorSubject to store the latest auth state. This lets you get the current user synchronously (if it's already loaded) or wait for it if not.
Update UserService:
import { Injectable } from '@angular/core'; import { AngularFireAuth } from 'angularfire2/auth'; import * as firebase from 'firebase'; import { BehaviorSubject, Observable } from 'rxjs'; import { first } from 'rxjs/operators'; @Injectable() export class UserService { private userSubject = new BehaviorSubject<firebase.User | null>(null); user$: Observable<firebase.User | null> = this.userSubject.asObservable(); userData: firebase.User | null = null; constructor(public afAuth:AngularFireAuth) { this.afAuth.authState.subscribe((user)=> { this.userData = user; this.userSubject.next(user); if(user){ console.log(user.displayName); } }); } signInWithGmail(){ this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()); } signOut(){ this.afAuth.auth.signOut(); } // Get user details as a promise (waits for auth state if not loaded) async getUserDetails(): Promise<firebase.User | null> { if (this.userData) { return this.userData; } // Wait for the first emission of the auth state return this.user$.pipe(first()).toPromise(); } }
Update AppComponent:
import { Component } from '@angular/core'; import { UserService } from './user.service' import { OnInit } from '@angular/core/src/metadata/lifecycle_hooks'; import * as firebase from 'firebase'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit{ userDetail: firebase.User | null; constructor(public userService: UserService){} async ngOnInit(){ // Wait for the user data to load before logging it this.userDetail = await this.userService.getUserDetails(); console.log('User details:', this.userDetail); } onSubmit(){ this.userService.signInWithGmail(); } onLogOut(){ this.userService.signOut(); console.log("Logged Out!"); } }
Key Takeaway
Always remember that Firebase auth operations (and most AngularFire operations) are asynchronous. Avoid trying to access async data synchronously—use Observables or Promises to handle the timing correctly.
内容的提问来源于stack exchange,提问作者Jack

