2

I don't understand why the child component's change detection runs in this scenario:

import { Component, ChangeDetectionStrategy } from '@angular/core'

@Component({
  selector: 'app-root',
  template: `
    <cmp [ticks]="ticks" (update)="onUpdate($event)"></cmp>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {

  ticks = 0;

  onUpdate(event) {
    console.log(this.ticks);
  }
}


import { Component, ChangeDetectionStrategy, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';

@Component({
  selector: 'cmp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<p>Number of ticks: {{ticks}}</p>
`
})
export class CmpComponent implements OnInit, OnChanges {
  @Input('ticks') ticks: number;
  @Output() update: EventEmitter<number> = new EventEmitter();

  ngOnInit() {
    setInterval(() => {
      this.ticks++;
      this.update.emit(this.ticks);
    }, 1000);
  }

  ngOnChanges() {
    console.log('changed');
  }
}

When I run this 'Number of ticks' is updated in the child view.

When I remove listening to the event in the parent it does not update the view.

What I understand is the following:

Because the parent implements the OnPush strategy it triggers change detection when it listens to the event that is emitted from the child. On receiving the event it does not alter 'tick' and therefore the child component's @Input() is not updated. Yet the child component which also implements OnPush strategy updates its view. It therefore acts as if its @Input changed.

From my research:

With the OnPush strategy the change detection happens for a component if:

  • a bound event is received on the component itself.
  • an @Input() was updated
  • | async pipe received an event
  • change detection was invoked "manually"

None of these seem to be applicable to the child component.

Any explanations? Much appreciated.

J. Reynolds
  • 151
  • 3
  • 14

1 Answers1

5

First of all thanks for a good question.

With angular 2.x.x it will work the way as you expect.

https://plnkr.co/edit/TiOeci5Lr49xvRB5ozHb?p=preview

But in angular4 was introduced new View Engine and all code was completelly overwritten.

https://plnkr.co/edit/SFruiPXEhMmYDP7WuBbj?p=preview

What has changed?

When event happens angular calls some method known as markForCheck.

angular 2 version

AppView.prototype.markPathToRootAsCheckOnce = function () {
  var /** @type {?} */ c = this;
  while (isPresent(c) && c.cdMode !== ChangeDetectorStatus.Detached) {
    if (c.cdMode === ChangeDetectorStatus.Checked) {
      c.cdMode = ChangeDetectorStatus.CheckOnce;
    }
    if (c.type === ViewType.COMPONENT) {
      c = c.parentView;
    }
    else {
      c = c.viewContainer ? c.viewContainer.parentView : null;
    }
  }
};

angular 4 version

function markParentViewsForCheck(view) {
  var /** @type {?} */ currView = view;
  while (currView) {
    if (currView.def.flags & 2 /* OnPush */) {
      currView.state |= 8 /* ChecksEnabled */;
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

Despite the fact that the code looks completely different there is no difference here. It starts with current component and enables check for all parent components up to root component.

I highlighted the phrase starts with current component because this is exactly what has changed.

Angular 2.x.x starts with AppComponent

View_App0.prototype.handleEvent_4 = function(eventName,$event) {
  var self = this;
  self.debug(4,2,3);
  self.markPathToRootAsCheckOnce(); // self is AppComponent view
  var result = true;
  if ((eventName == 'update')) {
    var pd_sub_0 = (self.context.onUpdate($event) !== false);
    result = (pd_sub_0 && result);
  }
  return result;
};

Angular 4 starts with CmpComponent

function dispatchEvent(view, nodeIndex, eventName, event) {
    var nodeDef = view.def.nodes[nodeIndex];
    var startView = nodeDef.flags & 33554432 /* ComponentView */ ? asElementData(view, nodeIndex).componentView : view;
    markParentViewsForCheck(startView);
    return Services.handleEvent(view, nodeIndex, eventName, event);
}

Therefore CmpComponent will be opened for check

yurzui
  • 205,937
  • 32
  • 433
  • 399