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 }
问题分析与解决方案
问题根源
- Resolver要求返回一个会完成的Observable,但你在
ofType(set_recipes)后用switchMap订阅store.select(selectAllRecipes)时,没有添加完成逻辑——这个选择器会持续监听状态变化,只要状态更新就会触发新的发射,导致Resolver无法正常完成,进而引发Angular重复触发Resolver逻辑,形成无限循环。 - 原废弃代码中,
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
相关产品推荐
相关产品推荐

