NGXS如何在异步调用后触发Angular重新渲染?

NGXS How to trigger Angular re-render after asynchronous call?

我正在从我的商店进行一些 api 调用,并且我有一个捕获错误,它在抛出错误时触发带有错误消息的模态。问题是,当发生这种情况时,触发模态的方法被调用,但 html 直到我点击页面上的某个地方才呈现。这只发生在商店内,我已经在应用程序的几个部分进行了模拟,如下所示:

timer(5000)
      .pipe(
        mergeMap(() => {
          throw new Error('Some error');
        }),
      )
      .pipe(
        catchError((error) => {
          return this.handleError(_(`Couldn't do the thing`))(error);
        }),
      )
      .subscribe((result) => {
        console.log(result);
      });

我以为我可以注入 ChangeDetectorRef 来触发 html 的手动重新渲染,但我得到了 NullInjectorError: No provider for ChangeDetectorRef!,但我无法让它工作。我的问题是:

是否可以在商店中注入 ChangeDetectorRef 是否可以解决我的问题?另外,作为后续问题,还有其他方法可以规避此问题吗?根据我一直在阅读的一些事情,这似乎是由于商店在 Angular 范围之外而发生的,所以它不知道需要重新渲染某些东西。

如有任何帮助,我们将不胜感激。

更新:Here 是一个 stackblitz,通过调度一个操作来显示错误消息来说明问题和可能的解决方案。

尝试返回 observable 而不是订阅它。这样 NGXS 将为您处理订阅并触发更改检测。请参阅下面的代码段:

@Action(SomeAction)
  public someAction(ctx: StateContext<{}>): void {
    return timer(2000) // returns the observable in the action handler 
      .pipe(
        first(), // completes after one emission
        mergeMap(() => {
          throw new Error("Some error");
        })
      )
      .pipe(
        catchError(error => {
          this.confimationService.triggerConfirmation();
          return of(undefined);
        })
      )
  }

将可观察对象返回给操作处理程序会将可观察对象绑定到 NGXS 中操作的生命周期。请确保此 Observable 最终完成,否则您的操作也将永远不会完成。

通常更改检测由 zone.js 自动触发,更具体地说,是在 NgZone 中注册的每个(微)任务之后。

NGXS 默认情况下不会 运行 NgZone 中的操作处理程序。这是一个有用的性能优化,在大多数情况下您不会注意到其中的差异。特别是当动作处理程序仅修改实际状态并且没有副作用时,情况更是如此。但在您的情况下,操作处理程序有副作用:this.handleError(_("Couldn't do the thing"))(error),或 StackBlitz 中的 confimationService.triggerConfirmation()。这种副作用甚至反映在视图中。

现在,还有很多方法可以解决这个问题。您所需要的只是 在副作用 之后触发的单个更改检测周期。这就是真正有趣的地方:虽然动作处理程序本身不在 NgZone 中 运行,但在 NgZone 中有很多周围的代码 运行ning。这可能确实会触发提到的变更检测周期。特别是:

  • 如果您的操作是同步的,并且调度它的代码 运行 在 NgZone 中,那么在当前(微)任务结束时触发的变更检测周期将 运行副作用后。
  • 如果您订阅从 store.dispatch 返回的 Observable,那么该订阅将在 NgZone 中发出并完成。因此,这会在副作用发生后触发两个变化检测周期。

(顺便说一句,后者是在您的 Stackblitz 中调度两个嵌套操作的原因:您在那里订阅调度方法!)

如果您想自己调查事件的顺序,请查看 this Stackblitz。控制台输出应该非常准确地告诉您每个场景中发生了什么。

最后,让我们谈谈如何确保正确触发变更检测。实际上有几个选项可供选择:

  1. 虽然不能在状态中注入 ChangeDetectorRef,但可以注入 ApplicationRef。调用其 tick 方法将在当前(微)任务结束时异步触发变化检测周期。
  2. 如果你想利用 zone.js,你也可以注入 NgZone 并使用它的 run 方法来 运行 你在 NgZone 中的整个副作用。这还有一个额外的好处,即由您的副作用注册的(微)任务也将跟随变更检测周期。
  3. 如果您想 运行 NgZone 中的所有代码,您可以在 NgxsConfig 中设置 executionStrategy: NoopNgxsExecutionStrategy。这将覆盖 NGXS 的默认行为并全局导致所有操作处理程序在 NgZone 中成为 运行。