ngOnChanges triggers following the modification of @Input bound class members. Data bound by the @Input() decorator come from an external source. When the external source alters that data in a detectable manner, it passes through the @Input property again.
By default, angular will run the change detector every time @Input() data is changed or modified. But with OnPush strategy, the change detector is only triggered if the data passed on @Input() has a new reference.
ngOnChanges() ( OnChanges ) is called when a value bound to an input has changed so you can run custom code when an input has changed. ngDoCheck() ( DoCheck ) is called when change detection runs so you can implement your custom change detection action.
When should you use ngOnChanges? Use ngOnChanges whenever you want to detect changes from a variable decorated by @Input. Remember that only changes from the parent component will trigger this function. Also remember that changes from the parent still update the child value even without implementing ngOnChanges.
rawLapsData
continues to point to the same array, even if you modify the contents of the array (e.g., add items, remove items, change an item).
During change detection, when Angular checks components' input properties for change, it uses (essentially) ===
for dirty checking. For arrays, this means the array references (only) are dirty checked. Since the rawLapsData
array reference isn't changing, ngOnChanges()
will not be called.
I can think of two possible solutions:
Implement ngDoCheck()
and perform your own change detection logic to determine if the array contents have changed. (The Lifecycle Hooks doc has an example.)
Assign a new array to rawLapsData
whenever you make any changes to the array contents. Then ngOnChanges()
will be called because the array (reference) will appear as a change.
In your answer, you came up with another solution.
Repeating some comments here on the OP:
I still don't see how
laps
can pick up on the change (surely it must be using something equivalent tongOnChanges()
itself?) whilemap
can't.
laps
component your code/template loops over each entry in the lapsData
array, and displays the contents, so there are Angular bindings on each piece of data that is displayed.===
checking), it still (by default) dirty checks all of the template bindings. When any of those change, Angular will update the DOM. That's what you are seeing.maps
component likely doesn't have any bindings in its template to its lapsData
input property, right? That would explain the difference.Note that lapsData
in both components and rawLapsData
in the parent component all point to the same/one array. So even though Angular doesn't notice any (reference) changes to the lapsData
input properties, the components "get"/see any array contents changes because they all share/reference that one array. We don't need Angular to propagate these changes, like we would with a primitive type (string, number, boolean). But with a primitive type, any change to the value would always trigger ngOnChanges()
– which is something you exploit in your answer/solution.
As you probably figured out by now object input properties have the same behavior as array input properties.
Not the cleanest approach, but you can just clone the object each time you change the value?
rawLapsData = Object.assign({}, rawLapsData);
I think I would prefer this approach over implementing your own ngDoCheck()
but maybe someone like @GünterZöchbauer could chime in.
In .ts
file (Parent component) where you are updating your rawLapsData
do it like this:
rawLapsData = somevalue; // change detection will not happen
Solution:
rawLapsData = {...somevalue}; //for Object, change detection will happen
rawLapsData = [...somevalue]; //for Array, change detection will happen
and ngOnChanges
will called in child component
As an extension to Mark Rajcok's second solution
Assign a new array to rawLapsData whenever you make any changes to the array contents. Then ngOnChanges() will be called because the array (reference) will appear as a change
you can clone the contents of the array like this:
rawLapsData = rawLapsData.slice(0);
I am mentioning this because
rawLapsData = Object.assign({}, rawLapsData);
didn't work for me. I hope this helps.
If the data comes from an external library you might need to run the data upate statement within zone.run(...)
. Inject zone: NgZone
for this. If you can run the instantiation of the external library within zone.run()
already, then you might not need zone.run()
later.
Use ChangeDetectorRef.detectChanges()
to tell Angular to run a change detection when you edit a nested object (that it misses with its dirty checking).
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