3

I'm trying to get familliar with angular 2's ChangeDetectionStrategy.OnPush performance boost (as explained here). But I have curios case here.

I have parent AppComponent:

@Component({
  selector: 'my-app',
  template: `<h1>
  <app-literals [title]="title" [dTitle]="dTitle"></app-literals>
  <input [value]="title.name"/>
</h1>
`
})
export class AppComponent implements OnInit {
  title = { name: 'original' };
  dTitle = { name: "original" };

  constructor(private changeDetectorRef : ChangeDetectorRef) {

  }

    ngOnInit(): void {
      setTimeout(() => {
        alert("About to change");
        this.title.name = "changed";
        this.dTitle = { name: "changed" };
      }, 1000);
    }

}

And child LiteralsComponent component:

@Component({
  selector: 'app-literals',
  template: `  {{title.name}}
  {{dTitle.name}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class LiteralsComponent implements OnInit {
  @Input('title') title;
  @Input('dTitle') dTitle;

  constructor() { }

  ngOnInit() {
  }

}

I thought setting strategy to OnPush made angular only reflect changes of references but in a sample I tried changing (mutate) a property of an object and angular still reflects it.

this.title.name = "changed"; shouldn't be detected (thus the UI shouldn't not reflect the change).

Here is the case on plunker

How comes? How to do it right?

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
Serge Intern
  • 2,669
  • 3
  • 22
  • 39
  • Possible duplicate of [What is ChangeDetectionStrategy in Angular2 and when to use OnPush Vs Default?](http://stackoverflow.com/questions/42542639/what-is-changedetectionstrategy-in-angular2-and-when-to-use-onpush-vs-default) – Roman C Apr 21 '17 at 09:41
  • I'm not asking what it is, I read about it and think I got it according to the book, I'm just not figuring out what I'm doing wrong. – Serge Intern Apr 21 '17 at 09:42
  • 1
    What causes the code in your question to be executed? Your question doesn't provide enough information. OnPush executes change detection when an change detection updated the value of an input or when an event happens inside the component. – Günter Zöchbauer Apr 21 '17 at 09:44
  • Remove `this.dTitle = { name: "changed" };` – yurzui Apr 21 '17 at 09:44
  • I mutate title instead of changing the whole object. That's why I excpect OnPush to prevent angular2 from seeing the property has changed. – Serge Intern Apr 21 '17 at 09:47
  • In your plunker you changes object – yurzui Apr 21 '17 at 09:47
  • I do this.title.name = "changed" and in my template I have {{title.name}}. Angular2 has to inspect inside "title" to know "name" changed. Yet ChangeDetectionStrategy.OnPush should prevent this kind of inspection's depth. – Serge Intern Apr 21 '17 at 09:51
  • Are you sure? http://take.ms/sBefe – yurzui Apr 21 '17 at 09:56
  • As I wrote in my above comment, there are cases where Angular runs change detection even with OnPush. If these cases apply isn't clear from the small amount of code you provided. – Günter Zöchbauer Apr 21 '17 at 09:56
  • dTitle reference change but title reference doesn't. Yet both {{title.name}} and {{dTitle.name}} are updated. – Serge Intern Apr 21 '17 at 09:58
  • read [this article on change detection](https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f) which explains how `onPush` works under the hood – Max Koretskyi Apr 21 '17 at 10:50
  • @SergeBuSI, see [my answer](http://stackoverflow.com/a/43540983/2545680) and tell me if there's anything unclear – Max Koretskyi Apr 21 '17 at 11:05
  • @GünterZöchbauer, _or when an event happens inside the component_ - what kind of event? – Max Koretskyi Apr 21 '17 at 11:06
  • @yurzui, he is not updating the right object – Max Koretskyi Apr 21 '17 at 11:07
  • @Maximus He is running change detection by changing reference in parent component – yurzui Apr 21 '17 at 11:08
  • @Maximus https://plnkr.co/edit/DnXP8TG9XZ257vT0TRes?p=preview If at least one of property has changed in parent component then component with OnPush change detection will run change detection He changed `dTitle` reference that is why his component is updated – yurzui Apr 21 '17 at 11:09
  • @yurzui, ah, yeah, I misunderstood the question. He is asking why the update if the object reference hasn't change, correct? – Max Koretskyi Apr 21 '17 at 11:13
  • Yes. That is. He wants only property to be updated but angular changes state for component and all view will be updated http://take.ms/GoBHk – yurzui Apr 21 '17 at 11:16
  • @Maximus a DOM event that Angular listens to. Not entirely sure if it only counts if the component itself has an event binding or if the event originates within the component but a parent has a binding. – Günter Zöchbauer Apr 21 '17 at 11:18
  • @Maximus http://stackoverflow.com/questions/39795634/angular-2-change-detection-and-changedetectionstrategy-onpush/39795679#39795679 http://stackoverflow.com/questions/42312075/change-detection-issue-why-is-this-changing-when-its-the-same-object-referen/42312239#42312239 – yurzui Apr 21 '17 at 11:19
  • @yurzui, I've figured it out, please see [my updated answer.](http://stackoverflow.com/a/43540983/2545680). I also find this question very interesting and informative for the public. I'll edit question details – Max Koretskyi Apr 21 '17 at 12:34
  • @GünterZöchbauer, I've figured it out, please see [my updated answer.](http://stackoverflow.com/a/43540983/2545680). I also find this question very interesting and informative for the public. I'll edit question details – Max Koretskyi Apr 21 '17 at 12:34

1 Answers1

8

If I understand correctly you're asking why the bindings value is updated in LiteralsComponent template even if you don't modify the reference title, but rather mutate the object.

The short answer is that because you modify both:

this.title.name = "changed";
this.dTitle = {name: "changed"};

in the AppComponent.ngOnInit. If you modify only this.title.name = "changed" you will see that template is not updated.

However, this is a very interesting question to explore in details

Let's first start with only this.title without this.dTitle.
The first thing to understand is that when you specify the following in the template:

{{title.name}}

here is what Angular does. It tries to find title object on current component instance, and then gets name property from it and reflects it in the DOM. But with the following configuration:

class AppComponent {
    title = { name: 'original' }

    ngOnInit(): void {
      setTimeout(() => {
        alert("About to change");
       this.title.name = "changed";
    }, 1000);
}
}

class LiteralsComponent {
     @Input() title;
}

the title object is the same in both components (point to the same memory location).

So when Angular runs change detection for the LiteralsComponent component, it accesses the same object that you're changing here in AppComponent:

ngOnInit(): void {
  setTimeout(() => {
    alert("About to change");
    this.title.name = "changed";
  }, 1000);
}

The interesting observation here is the change isn't detected at all neither with OnPush nor without it:

class LiteralsComponent {
     @Input() title;

     ngOnChanges(changes) {
         // will be triggered only for the first CD cycle,
         // and won't be triggered when `title` is updated
     }
}

Now, the last thing is to understand is when the DOM is updated. According to this article, it's updated during CD for the current component. It means that if the current component isn't checked, the DOM won't be updated. So we specify onPush for the LiteralsComponent:

changeDetection: ChangeDetectionStrategy.OnPush,

the view won't be updated.

But, it's updated in your question. Why?

And this where dTitle comes into play. With this property, you're actually modifying the reference and Angular detects bindings changes and runs CD for the LiteralsComponent component. And we learnt above that when CD is run, the DOM is updated. So Angular also updates {{title.name}} since it points to the same object in AppComponent, although it didn't detect it was changed.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488