Reputation: 2969
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?
Upvotes: 3
Views: 856
Reputation: 105497
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.
Upvotes: 8