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

Angular16迁移Resolve至ResolveFn后set_recipes无限调用问题排查

关于NgRx ResolveFn迁移后set_recipes无限调用的问题

已解决问题

原代码中RecipeActions枚举的FETCH_RECIPES与SET_RECIPES重复赋值,导致调用两者时触发同一动作,现已修复。

当前待解决问题

学习NgRx状态管理时,将课程中基于Resolve接口的废弃Resolver代码迁移至Angular 16的ResolveFn语法,已编写状态选择器及ResolveFn实现,但触发食谱获取时,set_recipes动作被无限调用。

废弃的Resolver代码

@Injectable({ providedIn: 'root' })
export class RecipesResolverService implements Resolve<Recipe[]> {
  constructor(
    private store: Store<fromApp.AppState>,
    private actions$: Actions
  ) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.store.select('recipes').pipe(
      take(1),
      map(recipesState => {
        return recipesState.recipes;
      }),
      switchMap(recipes => {
        if (recipes.length === 0) {
          this.store.dispatch(fetch_recipes());
          return this.actions$.pipe(
            ofType(set_recipes),
            take(1)
          );
        } else {
          return of(recipes);
        }
      })
    );
  }
}

已编写的状态选择器

// Create a feature selector for the 'recipes' slice of the AppState
export const selectRecipesState = createFeatureSelector<RecipesState>('recipes');

// Create a selector that gets the 'recipes' array from the RecipesState
export const selectAllRecipes = createSelector(
  selectRecipesState,
  (recipesState: RecipesState) => recipesState.recipes
); 

实现的ResolveFn代码

export const recipeResolver: ResolveFn<Recipe[]> = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
  const store = inject(Store<fromApp.AppState>);
  const actions$ = inject(Actions);

  return store.select(selectAllRecipes).pipe(
    take(1),  //'take(1)' ensures that the Observable completes after emitting the first item.
    switchMap(recipes => {
      if (recipes.length === 0) {
        store.dispatch(fetch_recipes()); //fetch recipes gets the recipes from a server using effects
        return actions$.pipe(
          ofType(set_recipes), //set recipes is dispatched when recipes are received from the server
          take(1),
          switchMap(() => store.select(selectAllRecipes))
        );
      } else {
        return of(recipes);
      }
    })
  );
}

存储的状态接口

export interface State {
  recipes: Recipe[];
  editedRecipe: Recipe,
  editedRecipeIndex: number
}

问题分析与解决方案

问题根源

  1. Resolver要求返回一个会完成的Observable,但你在ofType(set_recipes)后用switchMap订阅store.select(selectAllRecipes)时,没有添加完成逻辑——这个选择器会持续监听状态变化,只要状态更新就会触发新的发射,导致Resolver无法正常完成,进而引发Angular重复触发Resolver逻辑,形成无限循环。
  2. 原废弃代码中,set_recipes动作触发后直接返回该动作的Observable(take(1)确保完成),而新代码额外订阅了状态选择器,打破了原有的完成逻辑。

修复方案

方案一:直接使用动作payload(推荐)

如果你的set_recipes动作本身携带了recipes数据(即带有payload属性),可以直接提取该数据返回,无需再次订阅状态:

return actions$.pipe(
  ofType(set_recipes),
  take(1),
  map(action => action.payload) // 假设动作的payload字段存储了recipes数组
);

方案二:确保状态订阅完成

如果必须从状态中获取数据,给store.select(selectAllRecipes)添加take(1),强制Observable完成:

return actions$.pipe(
  ofType(set_recipes),
  take(1),
  switchMap(() => store.select(selectAllRecipes).pipe(take(1)))
);

额外检查

排查处理fetch_recipes的Effect逻辑,确认是否存在重复触发set_recipes的情况——比如Effect未正确使用switchMap、takeUntil等操作符,导致重复发起请求并触发动作,进而加剧循环。

内容的提问来源于stack exchange,提问作者leroyv

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 16:07:12