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。控制台输出应该非常准确地告诉您每个场景中发生了什么。
最后,让我们谈谈如何确保正确触发变更检测。实际上有几个选项可供选择:
- 虽然不能在状态中注入
ChangeDetectorRef
,但可以注入 ApplicationRef
。调用其 tick
方法将在当前(微)任务结束时异步触发变化检测周期。
- 如果你想利用 zone.js,你也可以注入
NgZone
并使用它的 run
方法来 运行 你在 NgZone 中的整个副作用。这还有一个额外的好处,即由您的副作用注册的(微)任务也将跟随变更检测周期。
- 如果您想 运行 NgZone 中的所有代码,您可以在
NgxsConfig
中设置 executionStrategy: NoopNgxsExecutionStrategy
。这将覆盖 NGXS 的默认行为并全局导致所有操作处理程序在 NgZone 中成为 运行。
我正在从我的商店进行一些 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。控制台输出应该非常准确地告诉您每个场景中发生了什么。
最后,让我们谈谈如何确保正确触发变更检测。实际上有几个选项可供选择:
- 虽然不能在状态中注入
ChangeDetectorRef
,但可以注入ApplicationRef
。调用其tick
方法将在当前(微)任务结束时异步触发变化检测周期。 - 如果你想利用 zone.js,你也可以注入
NgZone
并使用它的run
方法来 运行 你在 NgZone 中的整个副作用。这还有一个额外的好处,即由您的副作用注册的(微)任务也将跟随变更检测周期。 - 如果您想 运行 NgZone 中的所有代码,您可以在
NgxsConfig
中设置executionStrategy: NoopNgxsExecutionStrategy
。这将覆盖 NGXS 的默认行为并全局导致所有操作处理程序在 NgZone 中成为 运行。