You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:15:55