使用异步管道订阅Observable时如何处理ExpressionChangedAfterItHaHasBeenCheckedError?

时间:2019-08-15 13:28:03

标签: angular store ngrx-store angular8 ngrx-store-8.0

我有一个基于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
);

component.ts

  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);
    });
  }

component.html

<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">)获得的值,则不会出错。

我看到它说了关于undefinedfalse的内容,好像isLoading在初始化期间设置为false一样,但是初始状态直接定义为{{1} }。

如果绑定属性在一个函数中多次更改,我遇到了此错误,但是我在这里看不到我是怎么做的。

不确定是否重要,但我也在使用false元归约器来防止状态发生突变:

storeFreeze

问题:在使用异步管道订阅Observable时如何处理ExpressionChangedAfterItHaHasBeenCheckedError?

0 个答案:

没有答案
相关问题