Angular切换页面时BehaviorSubject丢失存储值问题如何解决?
问题1:多个组件重复创建独立的BehaviorSubject,未全局复用AuthService的单例实例
你在首页、登录页、Header组件中都写了currentUserSubject$ = new BehaviorSubject({} as UserAccount);,这行代码会创建和AuthService完全无关的本地独立Subject。即使首页在构造函数中把本地变量重新赋值为AuthService的Subject,如果登录页、Header组件没有做同样的赋值操作,这些组件用的就是自己的空Subject,自然拿不到AuthService里存储的用户数据,导致导航栏消失。
问题2:首页ngOnInit中的异步逻辑顺序错误,未发送更新通知
getNavigation是Http异步请求,你现有代码的执行顺序是:
- 创建空user对象,推送空值到Subject
- 发起导航请求(还没返回结果)
- 立刻推送还未赋值
navigation和isLogged的空user到Subject - 等到接口返回后,你修改了user对象的属性,但没有再调用
next()推送更新,所有订阅Subject的组件都收不到最新的导航数据,即使对象本身属性变了也不会触发视图更新。
问题3:AuthService初始化时未从本地存储恢复用户数据
你在updateAndEmitCurrentUser方法里把用户数据存入了localStorage,但AuthService实例化的时候,BehaviorSubject的初始值是空对象,没有从localStorage读取之前存储的用户数据,页面刷新、路由跳转后,Subject就会回到初始空值,导致已登录状态丢失。
问题4:嵌套subscribe写法存在内存泄漏风险(优化项)
多层嵌套subscribe不符合RxJS最佳实践,没有做取消订阅处理,长期运行会出现内存泄漏问题。
修复方案
1. 全局复用AuthService的Subject,组件内不要自行创建新实例
所有需要用到用户状态的组件,直接订阅AuthService暴露的currentUser$可观察对象即可,不要自己new BehaviorSubject:
// 正确的组件声明方式,不要加new的初始化 public currentUser$ = this.authService.currentUser$; constructor(private authService: AuthService) {}
视图层直接用async管道订阅即可:
<header *ngIf="currentUser$ | async as user"> <nav *ngFor="let nav of user.navigation">{{nav.name}}</nav> </header>
2. 修正首页ngOnInit的异步逻辑,接口返回后再推送更新
ngOnInit(): void { if(this.authService.currentUserValue.isLogged === undefined){ // 先发起导航请求 this.authService.getNavigation(this.userAccountType.Customer).subscribe((navigation: Navigation[]) =>{ const user = {} as UserAccount; user.navigation = navigation; user.isLogged = false; // 等所有属性赋值完成后再推送更新 this.authService.updateAndEmitCurrentUser(user); console.log('Header => ', JSON.stringify(user, null, 4)); }); } }
3. AuthService构造函数中增加本地存储恢复逻辑
constructor( private http: HttpClient) { this.currentUser$ = this.currentUserSubject$.asObservable(); // 初始化时从localStorage读取用户数据 const savedUser = localStorage.getItem('currentUser'); if (savedUser) { this.currentUserSubject$.next(JSON.parse(savedUser) as UserAccount); } }
4. 优化登录请求的嵌套subscribe,用switchMap打平异步流
onSubmit() { this.authService.LogIn(this.f.login.value, this.f.password.value).pipe( filter(user => !!user), switchMap(user => { return this.authService.getNavigation(user.userAccountTypeId.valueOf()).pipe( map(navigation => { user.navigation = navigation; user.isLogged = true; return user; }) ) }) ).subscribe(user => { this.authService.updateAndEmitCurrentUser(user); const truckId = user.truckId?.valueOf(); this.router.navigate([`/useraccount/${truckId}`]); }) }
内容的提问来源于stack exchange,提问作者Paintbox
相关产品推荐
相关产品推荐

