Jon Gunter
Jon Gunter

Reputation: 2104

When does ChangeDetectionStrategy.OnPush Actually Run Change Detection?

I'm building an Angular 4 app with many components where the ChangeDetectionStrategy is OnPush. While the Angular Documentation on the matter is void of much information, various sources say that OnPush components only update when their @Inputs change (new objects or primitives).

However, it seems that various events inside an OnPush component will also trigger change detection. I'm noticing others do not trigger change detection, however.

What are the specific rules for ChangeDetectionStrategy.OnPush in regards to events inside a component?

Upvotes: 6

Views: 3362

Answers (2)

Richard Matsen
Richard Matsen

Reputation: 23463

From Change Detection Strategy: OnPush,

This will inform Angular that our component only depends on its inputs and that any object that is passed to it should be considered immutable.

It goes on to show that property changes don't trigger change detection (considered immutable), but changes to the object itself does trigger change detection.

So it looks like the key criteria is immutability. The change triggers for an object referenced on the template are,

  • object reference change in code (either parent changes Input value or change within the child) will trigger change detection.
  • object property changes from outside the component will not trigger change detection.
  • object property changes from inside the component will trigger change detection.

I haven't tested a change from an observable, but that is given elsewhere as a trigger. (An observable emit changes an object reference, not it's properties).

Tested with angular version 5.2.0-beta.1


Extending the RangleIO example

The RangleIO page referenced at the top of the post has a Plunker which illustrates that objects passed in to a component via Input() parameters are considered immutable. The change detector of the child component will not respond to changes of Input object properties.

However, after duplicating the change buttons inside the Movie component, it can be seen that object property changes invoked inside the component's own code will change it's view.

Here's the code, Plunker

movie.component.ts

@Component({
  selector: 'app-movie',
  styles: ['div {border: 1px solid black}'],
  template: `
    <div>
      <h3>{{ title }}</h3>
      <p>
        <label>Actor:</label>
        <span>{{actor.firstName}} {{actor.lastName}}</span>
      </p>
      <button type="button" (click)="changeActorProperties()">
        Inside Movie Component - Change Actor Properties (will change view)
      </button>
      <button type="button" (click)="changeActorObject()">
        Inside Movie Component - Change Actor Object (will change view)
      </button>
    </div>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MovieComponent {
  @Input() title: string;
  @Input() actor: Actor;

  changeActorProperties(): void {
    this.actor.firstName = 'Nicholas';
    this.actor.lastName = 'Cage';
  }

  changeActorObject(): void {
    this.actor = new Actor('Bruce', 'Willis');
  }
}

app.component.ts

@Component({
  selector: 'app-root',
  template: `
    <h1>MovieApp</h1>
    <p>{{ slogan }}</p>
    <button type="button" (click)="changeActorProperties()">
      Outside Movie Component - Change Actor Properties (will not change Movie view)
    </button>
    <button type="button" (click)="changeActorObject()">
      Outside Movie Component - Change Actor Object (will change Movie view)
    </button>
    <app-movie [title]="title" [actor]="actor"></app-movie>`
})
export class AppComponent {
  slogan = 'Just movie information';
  title = 'Terminator 1';
  actor = new Actor('Arnold', 'Schwarzenegger');

  changeActorProperties(): void {
    this.actor.firstName = 'Nicholas';
    this.actor.lastName = 'Cage';
  }

  changeActorObject(): void {
    this.actor = new Actor('Bruce', 'Willis');
  }
}

Upvotes: 5

Martin Parenteau
Martin Parenteau

Reputation: 73721

This blog post of the Angular University contains several indications about the events that trigger change detection when using ChangeDetectionStrategy.OnPush:

An OnPush change detector gets triggered in a couple of other situations other than changes in component Input() references, it also gets triggered for example:

  • if a component event handler gets triggered
  • if an observable linked to the template via the async pipe emits a new value

They add the following recommendations:

So if we remember to subscribe to any observables as much as possible using the async pipe at the level of the template, we get a couple of advantages:

  • we will run into much less change detection issue using OnPush
  • we will make it much easier to switch from the default change detection strategy to OnPush later if we need to
  • Immutable data and @Input() reference comparison is not the only way to achieve a high performant UI with OnPush: the reactive approach is also an option to use OnPush effectively

In a comment following the question "Change Detection on Angular 2" on Disqus, Viktor Savkin explains:

When using OnPush detectors, then the framework will check an OnPush component when any of its input properties changes, when it fires an event, or when an observable fires an event.

Upvotes: 5

Related Questions