I'm trying to wrap my head around best practice when using Observables alongside ChangeDetectionStrategy.OnPush
.
The example demonstrates the common scenario of wanting to show some kind of loading message (or a simple spinner animation perhaps):
Plnkr here
@Component({ selector: 'my-app', template: `Are we loading?: {{loadingMessage}}`, // Obviously "Default" will notice the change in `loadingMessage`... // changeDetection: ChangeDetectionStrategy.Default // But what is best practice when using OnPush? changeDetection: ChangeDetectionStrategy.OnPush }) export class App implements OnInit { private loadingMessage = "Wait for it..."; constructor() { } ngOnInit() { // Pretend we're loading data from a service. // This component is only interested in the call's success Observable.of(true) .delay(2000) .subscribe(success => { if(success){ console.log('Pretend loading: success!'); // OnPush won't detect this change this.loadingMessage = 'Success!'; } }); } }
I more or less understand the requirement for immutability with OnPush
and, to me at least, it currently makes sense when talking about actual model data (likely held in some kind of store).
So, I have two questions:
'Success!'
trigger the change detector? As far as immutability is concerned, the value has changed, right?loadingMessage
) be implemented when using ChangeDetectionStrategy.OnPush
? If there are multiple best practices, please point me in the right direction.The OnPush strategy changes Angular's change detection behavior in a similar way as detaching a component does. The change detection doesn't run automatically for every component anymore. Angular instead listens for specific changes and only runs the change detection on a subtree for that component.
OnPush means that the change detector's mode will be set to CheckOnce during hydration. Default means that the change detector's mode will be set to CheckAlways during hydration.
So how do you implement OnPush strategy for a component? All you need to do is add the changeDetection parameter in their @Component annotation. import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ // ... changeDetection: ChangeDetectionStrategy.
detectChanges()linkChecks this view and its children. Use in combination with detach to implement local change detection checks.
Good question. I have two quotes from Savkin about onPush
(since the Angular.io docs don't seem to have any info on this topic yet):
The framework will check
OnPush
components only when their inputs change or components' templates emit events. -- refWhen using
OnPush
, Angular will only check the component when any of its input properties changes, when it fires an event, or when an observable fires an event. -- ref (in a comment reply to @vivainio)
The second quote seems more complete. (Too bad it was buried in a comment!)
Why doesn't the assignment of the new string value
Success!
trigger the change detector? As far as immutability is concerned, the value has changed, right?
OnPush
immutability is in reference to input properties, not normal instance properties. If loadingMessage
were an input property and the value changed, change detection would execute on the component. (See @Vlado's answer for Plunker.)
How should lightweight internal component state (i.e.,
loadingMessage
) be implemented when usingChangeDetectionStrategy.OnPush
? If there are multiple best practices, please point me in the right direction.
Here's what I know so far:
OnPush
component (i.e., the view will update). In your particular case, I was surprised that the view did not update. Here are two guesses as to why not: delay()
makes Angular look at it more like a setTimeout()
rather than an observable change. setTimeout
s do not result in change detection execution on an OnPush
component. In your example the Change Detector has completed its work 2 seconds before the value of loadingMessage
is changed.Observable
. I.e., maybe it is more than just "an observable fires"... maybe it has to be a bound observable that fires. In which case, creating loadingMessage
(or even a more generic state
property) as an Observable
itself will allow you to bind your template to its value (or multiple async values), see this example plnkr.| async
, as shown in the plnkr, and in this plnkr.ChangeDetectorRef
into our component and call method markForCheck()
to cause change detection to execute on the OnPush
component and all ancestor components up to the root component. If only view state is changed (i.e., state that is local to the component and maybe its descendants), detectChanges()
can be used instead, which will not mark all ancestor components for change detection. Plunker If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With