Search code examples
angularangular2-changedetection

DOM is getting updated for a view with OnPush strategy during change detection cycle even though Input is not changed


Perhaps I'm missing something in Change Detection mechanism in Angular.

I have 2 components:

AppComponent.

@Component({
  selector: 'my-app',
  template: `
    <button
      (click)="0">
      Async Action in Parent
    </button>

    <child
      [config]="config">
    </child>

    <button
      (click)="mutate()">
      Mutate Input Object
    </button>

  `
})
export class AppComponent  {
  config = {
    state: 0
  };

  mutate() {
    this.config.state = 1;
    console.log(this.config.state);
  }
}

and ChildComponent

@Component({
  selector: "child",
  template: `
    <h1>{{config.state}}</h1>

    <button
      (click)="0">
      Async Action in Child
    </button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})

export class ChildComponent {
  @Input() config;

  ngDoCheck() {
    console.log(`Check`);
  }
}

Step 1.

Here, on clicking a button from AppComponent I invoke mutate function which in turn mutates the object which is passed to a nested ChildComponent.

Step 2.

Then I click the other button from the same AppComponent just in order to trigger Change Detection cycle. Since ChildComponent is using OnPush strategy and input reference didn't change then during the following Change Detection ChildComponent's view is not checked and DOM interpolations are not updated. And up to this point it behaves as I expect it.

But if on Step 2. I trigger Change Detection from within ChildComponent (by clicking appropriate button) then ChildComponent's view is checked and DOM interpolations are updated. Why is it so? Nothing has changed since the previous time.

Here is the demo on StackBlitz.

I've read a great article on Change Detection by Maxim Koretskyi but unfortunately it didn't answer my question.


Solution

  • Think of the change detection cycle as follows:

    1. Something needs to signal Angular to check if anything changed in the view,
    2. When Angular receives such signal, Angular will check if anything changed in the view.

    So, as bryan60 pointed out, there are a number of events that signal Angular to check the view when using OnPush: @Output event, click event, scroll event, mouseover event, etc... One other event that I will point out in this list is a change to the @Input value. As you already pointed out, Angular did not check the view when the config object was mutated because mutations are not an actual change. Therefore the @Input did not trigger Angular to check the view. The subsequent click event however did signal Angular to check the view. Now, I think here comes the important part: Once Angular received the signal to check the view, it checks the view bindings, not the @Input value. In other words, the click event caused Angular to check if {{ config.state }} changed, and not if the @Input value (i.e. config) changed.