从 ngOnChanges 更改组件输入,同时使用 OnPush 策略

Nao*_*mor 5 rxjs ngrx angular2-changedetection angular angular-changedetection

我在使用 Angular 6 应用程序时遇到问题:

问题

假设我有 2 个组件:父组件和子组件。

孩子有2个输入。当 1 个输入发生变化时,在 ngOnChanges() 中,子组件向父组件发出一些东西。

然后父组件更改子组件的第二个输入。我希望再次调用子组件的更改检测 - 但事实并非如此。子组件的视图显示了第二个输入的旧值。


辩解

该申请有以下定义:

  1. 所有组件都使用变更检测策略 OnPush。包括根组件。
  2. 我正在使用 ngrx 商店。在减速器中调用 markForCheck() 或 detectChanges() 不是一个选项。
  3. 数据使用异步管道向下传输到子组件,因为它来自使用选择器(可观察)的存储。
  4. 我不能更早地导致第二个输入的更改(例如在父组件中)-它必须在子组件接收到第一个输入的更改之后发生。

为了说明这个案例,我创建了一个简单的演示项目。在那里我不使用商店,所以我没有任何选择器......相反,我使用 rxjs Subject 来使用异步管道。

这里是:

https://angular-ccejq4.stackblitz.io

查看控制台以了解会发生什么以及何时发生。


糟糕的解决方案

我已经尝试过的:

  1. 将根组件的更改检测策略更改为默认值。它有效,但这不是一个好的解决方案,因为它会导致性能问题。
  2. 在发出(!)之前和之后调用 markForCheck() - 不工作。
  3. 在发出(!)之前和之后调用detectchanges() - 不工作。
  4. 在父组件订阅选择器而不是使用异步管道 - 不工作。
  5. 从 setTimeout 中调用发射 - 它有效。但有更好的解决方案吗?一个更 Angular 驱动的。因为这个解决方案更像是一种解决方法。
  6. 使用 ngAfterViewInit/ngAfterViewChecked 并发出,从那里调用 markForCheck() 和 detectChanges() - 不工作。

我发现新值到达异步管道。然后异步管道调用 markForCheck()。但它不会调用detectChanges()..所以更改检测周期不会运行。视图只会在下一个更改检测周期时正确更改 - 正如您在我的演示应用程序中看到的那样。


有任何想法吗?

谢谢!

Pie*_*Duc 6

首先我要说的是,这是一个很好的问题,详细且解释得很好。您确实遇到了角度变化检测的警告。

从内部ngOnChanges没有直接的方法来触发另一个变化检测。否则你将有可能陷入循环。此外,在性能方面他们也不允许这样做。你需要想办法进入JS的下一个执行周期

setTimeout克服这个问题并不是一个坏的选择。您可以将其放在发射周围:

setTimeout(() => this.secondEmitter.emit(this.first));
Run Code Online (Sandbox Code Playgroud)

或者你可以把它放在markForCheck通话中:

setTimeout(() => this.cdRef.markForCheck());
Run Code Online (Sandbox Code Playgroud)

但我同意你的观点,这感觉有点老套,但有时你只需要应对它。如果实在不行的话,还有别的办法。你BehaviorSubject本质上是一个同步可观察的。async但是,您可以在模板中进行订阅以使用asyncScheduler

private second = new BehaviorSubject(0);

readonly second$ = this.second.asObservable().pipe(
  observeOn(asyncScheduler)
)

constructor() {
  this.second.next(0);
  this.second$.subscribe((lastval) => {
    console.log(`second subscription update to ${lastval}`);
  });
}
Run Code Online (Sandbox Code Playgroud)

从上一个选项开始,我已经对你的堆栈进行了分叉

这将使任何对其的订阅在下一个事件循环周期后收到消息。


更新

回到这一点,更好的解决方案是使用async创建EventEmitter.

在这种情况下,您可以.emit在第二个可观察量上使用:

@Output() firstEmitter:EventEmitter<number> = new EventEmitter();

// notice the 'true'
@Output() secondEmitter:EventEmitter<number> = new EventEmitter(true);

ngOnChanges(changes:any) {
  if (changes.first) {
    this.secondEmitter.emit(this.first);
  }
}
Run Code Online (Sandbox Code Playgroud)

工作示例