使用ChangeDetectionStrategy.OnPush在setTimeout中不会触发更改检测

时间:2017-07-24 13:06:32

标签: angular

我有两个组件,我使用ChangeDetectionStrategy.OnPush。父组件:

@Component({
 changeStaregy: ChangeDetectionStrategy.OnPush,
 template:`
    <button (click)="onClick()">clear</button>
    <div>
        <test [model]="model"></test>
    </div>`
})
export class AppComponent {
    model: TestModel;

    constructor(){
      this.model = { id: 1, text: 'bla bla bla'}
    }

    onClick() {
      this.model = new TestModel();
    }
}

和只显示数据的子组件:

@Component({
   changeStrategy: ChangeDetectionStrategy.OnPush,
   selector: 'test',
   template: `
    <div>
          <div> {{model.id}} </div>
           <div> {{model.text}} </div>
    </div>`
})

export class TestComponent {
   @Input() model: TestModel;

}

当我点击“清除”按钮时,它会调用onClick()函数,它将一个空实体分配给“模型”。这会触发更改检测,因为输入已更改(OnPush策略)。但是,如果我使用异步调用来包装赋值,则更改检测不起作用,因此UI不会更新:

onClick() {
  setTimeout(() => {
    this.model = new TestModel();
  }, 2000);
}

Angular2 +具有修补setTimeout函数的NgZone。修补后的setTimeout必须触发更改检测,但在我的情况下却没有。为什么变化检测不起作用?我该如何解决?

4 个答案:

答案 0 :(得分:2)

由于父项上的changeDetectionStrategy设置为OnPush,因此更改检测周期将停止在父元素处。因此,此父级的任何子级都将策略设置为OnPush,而不管其自身设置如何。

父级的@Input没有更改,因此更改检测器不会更深入地朝向父级的子级。您应该执行detectChanges以使更改生效

答案 1 :(得分:1)

使用OnPush更改检测策略时,更改检测仅在输入属性更改,绑定事件是从组件触发或手动检测更改时运行。

在您的示例plunker中,子组件在changeSetTimeoutchangeModelInZone中自行更改输入。

当您点击按钮时,由于更新了哪个模板,第二次触发有界事件。

答案 2 :(得分:1)

以下是与您的场景中发生的情况有关的步骤:

  1. 绑定的单击事件在触发时会将组件及其祖先标记为脏。这发生在 onClick() 方法执行之前。
  2. onClick() 方法执行后,Angular 会调用 ApplicationRef.tick() 来启动变更检测。因为祖先组件被标记为脏,所以更改检测发生在您的 OnPush 组件上。请记住,此时尚未执行 setTimeout 回调。
  3. 变更检测完成后,组件不再“脏”。 然后执行 setTimeout 回调。在 setTimeout 回调完成后,将调用 ApplicationRef.tick() 再次启动更改检测。 但是,因为您的 AppComponent 被标记为 OnPush,并且没有 @Input 更改(因为 AppComponent 没有 Inputs),并且组件及其祖先没有被标记为脏,所以更改检测不会传递到子组件成分。因此对 this.model 的更改不会反映在 DOM 中。

我发现这很有帮助: https://www.mokkapps.de/blog/the-last-guide-for-angular-change-detection-you-will-ever-need

答案 3 :(得分:0)

您可以使用承诺来保持变更检测正常工作。

  onDoSometing() {
   this.changeVariable = -1; // sample immediate change

   this.timeout(3000).then(v => {
    this.changeVariable = 2; //sample, put your code here
   });
  }



  timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }