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

Angular Ngrx Effect内重定向引发异常问题求助

问题分析与解决思路

这个异常的核心原因是尝试修改不可扩展(non-extensible)的对象,结合你的场景,最可能的触发点是路由守卫中直接引用了Firebase返回的不可扩展User实例,或者导航与守卫的时序冲突导致Angular内部尝试修改该对象。以下是具体的解决方向:

1. 复制Firebase User对象,避免直接引用不可扩展实例

Firebase的User对象是被冻结/密封的(不可扩展),当你的路由守卫返回包含原User实例的对象时,Angular Fire的守卫管道可能会尝试对该对象添加内部属性,从而触发Cannot add property 0, object is not extensible错误。

修改守卫代码,将原User实例浅拷贝为可扩展的普通对象:

export const userSpaceGuard = () =>
  pipe(
    switchMap(async (user: User | null) => {
      if (!user) {
        return null;
      }

      // 浅拷贝User对象的属性,生成可扩展的新对象
      const userCopy = { ...user };
      return {
        user: userCopy,
        idTokenResult: await user.getIdTokenResult(),
      };
    }),
    // ... 后续逻辑保持不变
  );

2. 改用UrlTree作为守卫的重定向返回值

直接返回路由路径数组可能导致Angular内部对返回值进行额外处理,改用UrlTree可以避免这种潜在的对象修改操作,同时让守卫逻辑更清晰:

import { inject, Router } from '@angular/router';

export const userSpaceGuard = () => {
  const router = inject(Router);
  
  return pipe(
    switchMap(async (user: User | null) => {
      // ... 原有逻辑
    }),
    map((userInfos: any) => {
      if (!userInfos?.user) {
        // 返回UrlTree而非路径数组
        return router.createUrlTree(['auth/login']);
      }

      const claims = userInfos.idTokenResult.claims;
      if (claims && claims.role === UserRole.Admin) {
        return router.createUrlTree(['admin']);
      }

      return true;
    })
  );
};

3. 确保导航在用户状态完全更新后执行

即使在userLoaded的Effect中执行导航,也需要确保状态已经完成更新。可以通过withLatestFrom从Store中获取用户状态,确认存在后再触发导航:

// 假设你有一个选择器用于获取当前用户
import { selectCurrentUser } from './auth.selectors';

loginSuccess$ = createEffect(() =>
  this.actions$.pipe(
    ofType(AuthLoginActions.loginSuccess),
    switchMap(({ credentials }) =>
      this.authService.me().pipe(
        map((user: MeQuery['me']) => AuthActions.userLoaded({ user })),
        catchError((error) => of(AuthActions.error({ error })))
      )
    ),
    // 等待Store中的用户状态更新完成
    withLatestFrom(this.store.select(selectCurrentUser)),
    filter(([_, user]) => !!user),
    tap(() => this.router.navigate(['/']))
  )
);

4. 确保Apollo返回的用户对象可扩展

如果Apollo开启了缓存对象冻结(比如cacheOptions.freeze: true),返回的用户对象会是不可扩展的。可以在me()方法中复制对象:

me(): Observable<MeQuery['me']> {
  return this.meGql.watch().valueChanges.pipe(
    map((result) => {
      // 复制对象,解除冻结
      return { ...result.data.me };
    })
  );
}

内容的提问来源于stack exchange,提问作者Jérémie Dutheil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 13:34:52