我有一个基于ngRx / store的简单加载程序机制:
import { Action, createAction, props } from "@ngrx/store";
export const LoadingStartedAction = createAction("[Loading] Loading started", props<{ message: string }>());
export const LoadingEndedAction = createAction("[Loading] Loading ended");
export interface LoadingState {
isLoading: boolean;
loaderMessage: string;
}
export const initialLoadingState: LoadingState = {
isLoading: false,
loaderMessage: ""
};
export const loadingReducerInner = createReducer(
initialLoadingState,
on(LoadingStartedAction, (state) => {
return {
isLoading: true,
loaderMessage: state.loaderMessage
};
}),
on(LoadingEndedAction, _ => {
return {
isLoading: false,
loaderMessage: ""
};
})
);
export function loadingReducer(state: LoadingState, action: Action) {
return loadingReducerInner(state, action);
}
export const selectLoadingState = (state: AppState) => state.loading;
export const isLoadingSelector = createSelector(
selectLoadingState,
loading => loading.isLoading
);
export const loadingMessageSelector = createSelector(
selectLoadingState,
loading => loading.loaderMessage
);
isLoading$: Observable<boolean>;
isLoading: boolean;
loadingMessage$: Observable<string>;
loadingMessage: string;
ngOnInit(): void {
this.isLoading$ = this.store.pipe(select(isLoadingSelector));
this.loadingMessage$ = this.store.pipe(select(loadingMessageSelector));
this.isLoading$.subscribe(l => {
console.log("Loading value: ", l);
setTimeout(() => {
this.isLoading = l;
}, 0);
});
this.loadingMessage$.subscribe(lm => {
console.log("Loading message: ", lm);
setTimeout(() => {
this.loadingMessage = lm;
}, 0);
});
}
<div [hidden]="!(isLoading$ | async)" class="overlay displayO">
<div class="overlay-info">
<mat-spinner class="spinner"></mat-spinner>
<!-- throws error {{loadingMessage$ | async}} -->
{{loadingMessage}}
</div>
</div>
loadQuestionsPage$ = createEffect(() =>
this.actions$.pipe(
ofType(QuestionsPageRequestedAction),
mergeMap(payload => {
this.logger.logTrace("loadQuestionsPage$ effect triggered for type QuestionsPageRequestedAction");
this.store.dispatch(LoadingStartedAction({ message: "Loading question list ..."}));
return this.questionService.loadQuestionsPage(payload.page.pageIndex, payload.page.pageSize).pipe(
tap(_ => this.store.dispatch(LoadingEndedAction())),
catchError(err => {
this.store.dispatch(LoadingEndedAction());
this.logger.logErrorMessage("Error loading questions: " + err);
this.store.dispatch(QuestionsPageCancelledAction());
return of(<QuestionListModel[]>[]);
})
);
}),
map(questions => {
const ret = QuestionsPageLoadedAction({ questions });
this.logger.logTrace("loadQuestionsPage$ effect QuestionsPageLoadedAction: ", ret);
return ret;
})
)
);
Redux开发人员工具显示单个LoadingStartedAction
和单个LoadingEndedAction
。
如果我使用异步,则会收到错误消息:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'false'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?
at throwErrorIfNoChangesMode (core.js:6453) [angular]
at bindingUpdated (core.js:16425) [angular]
at bind (core.js:16524) [angular]
at ɵɵproperty (core.js:16506) [angular]
at AppComponent_Template (template.html:46) [angular]
at executeTemplate (core.js:9596) [angular]
at checkView (core.js:11020) [angular]
at componentRefresh (core.js:10778) [angular]
at refreshChildComponents (core.js:9277) [angular]
at refreshDescendantViews (core.js:9176) [angular]
at renderComponentOrTemplate (core.js:9567) [angular]
at tickRootContext (core.js:10926) [angular]
at detectChangesInRootView (core.js:10962) [angular]
at checkNoChangesInRootView (core.js:10992) [angular]
但是,如果使用通过显式订阅(即<div [hidden]="!isLoading" class="overlay displayO">
)获得的值,则不会出错。
我看到它说了关于undefined
和false
的内容,好像isLoading
在初始化期间设置为false
一样,但是初始状态直接定义为{{1} }。
如果绑定属性在一个函数中多次更改,我遇到了此错误,但是我在这里看不到我是怎么做的。
不确定是否重要,但我也在使用false
元归约器来防止状态发生突变:
storeFreeze
问题:在使用异步管道订阅Observable时如何处理ExpressionChangedAfterItHaHasBeenCheckedError?