如何在Ngrx中实现请求时IsLoading控制及执行顺序保证?
解决Ngrx单Action触发Reducer与Effect并控制执行顺序的方案
核心实现思路
Ngrx的设计本身就支持同一个Action同时被Reducer和Effect监听,而且Reducer是同步执行的,Effect是异步执行的,天然就能保证先执行Reducer设置isLoading=true,再触发Effect的异步操作,完全符合你的需求,不需要额外处理顺序问题。
具体代码实现步骤
1. 定义Action
先创建三个Action:触发操作的主Action、请求成功/失败的结束Action:
// actions.ts import { createAction, props } from '@ngrx/store'; import { YourModel } from './your.model'; // 用户触发的主Action export const updateObject = createAction( '[Your Feature] Update Object', props<{ payload: YourModel }>() ); // 请求成功后触发的Action export const updateObjectSuccess = createAction( '[Your Feature] Update Object Success', props<{ payload: YourModel }>() ); // 请求失败后触发的Action(可选,用于错误处理) export const updateObjectFailure = createAction( '[Your Feature] Update Object Failure', props<{ error: any }>() );
2. Reducer处理状态更新
Reducer同步监听主Action,设置isLoading=true;同时监听成功/失败Action,设置isLoading=false并更新数据:
// reducers.ts import { createReducer, on } from '@ngrx/store'; import * as YourActions from './actions'; import { initialState, YourState } from './state'; export const yourReducer = createReducer( initialState, // 主Action触发时,同步设置isLoading为true,可暂存更新的数据 on(YourActions.updateObject, (state, { payload }) => ({ ...state, isLoading: true, // 可选:先在本地显示更新后的数据,后续用服务器返回数据覆盖 data: payload })), // 请求成功后,更新数据并关闭loading on(YourActions.updateObjectSuccess, (state, { payload }) => ({ ...state, isLoading: false, data: payload, error: null })), // 请求失败后,关闭loading并记录错误 on(YourActions.updateObjectFailure, (state, { error }) => ({ ...state, isLoading: false, error: error })) );
3. Effect处理异步请求
Effect监听主Action,执行服务器请求,完成后分发成功/失败Action:
// effects.ts import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import * as YourActions from './actions'; import { YourService } from './your.service'; @Injectable() export class YourEffects { updateObject$ = createEffect(() => this.actions$.pipe( ofType(YourActions.updateObject), // 取出Action中的数据,调用服务发起请求 switchMap((action) => this.yourService.saveOrFetchData(action.payload).pipe( // 请求成功,分发成功Action map((response) => YourActions.updateObjectSuccess({ payload: response })), // 请求失败,分发失败Action catchError((error) => of(YourActions.updateObjectFailure({ error }))) ) ) ) ); constructor(private actions$: Actions, private yourService: YourService) {} }
4. 组件中使用
组件通过selector获取状态,根据isLoading控制UI阻塞:
// your.component.ts import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { selectYourData, selectIsLoading } from './selectors'; import * as YourActions from './actions'; import { YourModel } from './your.model'; @Component({ selector: 'app-your', template: ` <div *ngIf="isLoading$ | async" class="loading-overlay">加载中...</div> <form (ngSubmit)="onSubmit(formValue)"> <!-- 表单控件 --> <button type="submit" [disabled]="isLoading$ | async">保存</button> </form> ` }) export class YourComponent { isLoading$ = this.store.select(selectIsLoading); data$ = this.store.select(selectYourData); constructor(private store: Store) {} onSubmit(formValue: YourModel) { // 只需要分发这一个Action即可完成全流程 this.store.dispatch(YourActions.updateObject({ payload: formValue })); } }
方案可行性说明
- Reducer同步优先:分发
updateObjectAction时,Ngrx会先同步执行Reducer逻辑,立刻将isLoading设为true,组件能瞬间感知并阻塞UI。 - Effect异步后置:Reducer执行完成后,Effect才会监听该Action并启动异步请求,顺序完全有保障。
- 单一Action触发:用户只需要触发一个Action,就能完成“开启loading + 发起请求 + 关闭loading”的完整流程,符合你的需求。
可选优化点
- 乐观更新:Reducer处理
updateObject时先更新data字段,让用户立刻看到操作效果,后续用服务器返回数据覆盖,提升体验。 - 错误恢复:在
updateObjectFailure的Reducer逻辑中,可恢复之前的数据状态,避免用户看到错误数据。 - 加载状态细分:将
isLoading设为枚举值(如'idle' | 'saving' | 'fetching'),更精准控制不同操作的UI状态。
内容的提问来源于stack exchange,提问作者emery.noel
相关产品推荐
相关产品推荐

