Angular路由切换用户状态丢失,如何实现全局持久化登录态?
解决Angular路由切换时用户状态丢失的问题
切换路由时用户状态被清除,认证功能无法正常工作,跳转至其他组件时出现
user == undefined的情况。如何实现登录后用户状态在整个项目中保持直至执行登出操作?
问题分析
你的AuthService存在以下几个导致状态丢失的核心问题:
ngOnDestroy中手动清除用户状态:如果AuthService是全局单例,它的销毁逻辑不该主动清除用户状态;如果是组件级注入,路由切换会连带销毁服务导致状态丢失。- 初始化未从本地存储恢复状态:BehaviorSubject初始值为
undefined,页面刷新或路由切换后没有读取之前保存的用户信息。 onAuthStateChanged订阅放在登录方法内:只有登录时才监听Auth状态,页面刷新或路由切换后无法自动同步用户状态。- 冗余的
user变量订阅:直接通过user$Observable获取状态即可,无需额外同步到user变量。
修复方案
1. 确保AuthService是全局单例
在服务的@Injectable装饰器中设置providedIn: 'root',保证整个应用只有一个实例:
@Injectable({ providedIn: 'root' }) export class AuthService implements OnDestroy { // ... 现有代码 }
2. 移除ngOnDestroy中的状态清除逻辑
全局单例服务只需取消Firebase Auth的状态监听,不需要清除用户状态:
ngOnDestroy(): void { // 取消Firebase Auth状态监听 if (this.unsubscribe) { this.unsubscribe(); } this.subscription.unsubscribe(); }
3. 在构造函数中初始化状态并全局监听Auth变化
启动时先从localStorage恢复用户信息,再全局监听Auth状态变化,保证页面刷新或路由切换后状态自动同步:
constructor(private afa: AngularFireAuth) { // 从本地存储恢复用户状态 const savedUser = localStorage.getItem(this.USER_KEY); if (savedUser) { this.user$$.next(JSON.parse(savedUser)); } // 全局监听Firebase Auth状态变化 this.unsubscribe = onAuthStateChanged(getAuth(), (user) => { if (user) { this.user$$.next(user); localStorage.setItem(this.USER_KEY, JSON.stringify(user)); } else { this.user$$.next(undefined); localStorage.removeItem(this.USER_KEY); } }); // 移除冗余的user变量订阅(无需同步到本地变量) // this.subscription = this.user$.subscribe((user) => { // this.user = user; // }); }
4. 优化登录方法,移除重复监听
登录方法中无需手动更新状态或存本地存储,全局监听会自动处理:
loginUser = async (email: string, password: string) => { const auth = getAuth(); try { const userCredential = await signInWithEmailAndPassword(auth, email, password); return userCredential.user; } catch (error) { console.error("Error logging in:", error); throw error; } };
5. 组件中通过Observable获取状态
组件中使用user$ Observable配合async pipe获取状态,保证实时响应:
// 组件代码示例 user$ = this.authService.user$;
<!-- 模板代码示例 --> <div *ngIf="user$ | async as user"> Welcome, {{ user.email }} </div>
最终修改后的AuthService代码
import { Injectable, OnDestroy } from '@angular/core'; import { AngularFireAuth } from '@angular/fire/compat/auth'; import { BehaviorSubject, Subscription } from 'rxjs'; import { getAuth, signInWithEmailAndPassword, onAuthStateChanged, updateEmail } from "firebase/auth"; import { getFirestore, collection, query, where, getDocs, setDoc, doc, getDoc, updateDoc } from "firebase/firestore"; import { Unsubscribe } from '@firebase/util'; interface User { uid: string; email?: string; } @Injectable({ providedIn: 'root' }) export class AuthService implements OnDestroy { private user$$ = new BehaviorSubject<User | undefined>(undefined); public user$ = this.user$$.asObservable(); private unsubscribe!: Unsubscribe; private subscription?: Subscription; isEditMode: boolean = false; USER_KEY = '[user]'; get isLogged(): boolean { return this.user$$.value !== undefined; } constructor(private afa: AngularFireAuth) { // 从本地存储恢复用户状态 const savedUser = localStorage.getItem(this.USER_KEY); if (savedUser) { this.user$$.next(JSON.parse(savedUser)); } // 全局监听Firebase Auth状态变化 this.unsubscribe = onAuthStateChanged(getAuth(), (user) => { if (user) { const userData: User = { uid: user.uid, email: user.email }; this.user$$.next(userData); localStorage.setItem(this.USER_KEY, JSON.stringify(userData)); } else { this.user$$.next(undefined); localStorage.removeItem(this.USER_KEY); } }); } signUp(email: string, password: string, mobile: string) { const db = getFirestore(); const usersRef = collection(db, "users"); const q = query(usersRef, where("phoneNumber", "==", mobile)); return getDocs(q).then(async (querySnapshot) => { if (!querySnapshot.empty) { throw new Error("This phone number is already registered."); } else { const userCredential = await this.afa.createUserWithEmailAndPassword(email, password); const uid = userCredential.user?.uid; if (uid) { await setDoc(doc(db, "users", uid), { phoneNumber: mobile }); } } }); }; loginUser = async (email: string, password: string) => { const auth = getAuth(); try { const userCredential = await signInWithEmailAndPassword(auth, email, password); return userCredential.user; } catch (error) { console.error("Error logging in:", error); throw error; } }; signOut() { return this.afa.signOut(); } getProfile() { return this.user$$.value; } async getUserDetails() { const user = this.user$$.value; if (!user) { throw new Error("User not logged in"); } const db = getFirestore(); const docRef = doc(db, "users", user.uid); const snapshot = await getDoc(docRef); const userData = snapshot.data(); return { phoneNumber: userData?.['phoneNumber'] || "", address: userData?.['address'] || "" }; } async updateProfile(user: any) { const currentUser = this.user$$.value; if (!currentUser) { throw new Error("User not logged in"); } if (user.email) { const auth = getAuth(); await updateEmail(auth.currentUser!, user.email); window.alert("Email changed!"); // 更新本地状态 currentUser.email = user.email; this.user$$.next(currentUser); localStorage.setItem(this.USER_KEY, JSON.stringify(currentUser)); } if (user.phoneNumber || user.address) { const db = getFirestore(); const userRef = doc(db, "users", currentUser.uid); const updateData: any = {}; if (user.phoneNumber) updateData.phoneNumber = user.phoneNumber; if (user.address) updateData.address = user.address; await updateDoc(userRef, updateData); window.alert("Profile updated!"); } } ngOnDestroy(): void { if (this.unsubscribe) { this.unsubscribe(); } this.subscription?.unsubscribe(); } }
内容的提问来源于stack exchange,提问作者user28628241
相关产品推荐
相关产品推荐

