Change Detection | Immutability
Change Detection => Process run by angular to check whether the app's state has been changed. Happens from top to bottom. It runs periodically so that changes in the logic/components/data models are getting reflected in the view. {also happens on data binding - component to view/view to component}
Triggered manually on some events or asynchronously also (API calls or async events in DOM etc). more examples -{mouse move, HTTP call, setInterval promise}. Also, angular monitors after every few ms if there has any change happened.
Change Detection -> highly optimized, performant but can cause issues if being run too much.
Motive => to render fresh and the latest content on the screen.
Immutability => It's something which is not susceptible to any kind of change, In programming, it's defined as something which can't be changed after it's been set. Example , in JS, -> Boolean, number, string, symbol, null, undefined are immutable. Mutable -> Arrays, Objects, functions.
->
-> Since, Arrays & objects are mutable by default, which means they can be modified in place whenever we do ay operation on them -> example - push, pop, delete etc.
->
-> After pushing, it's changed in place.
-> need of Immutability
Embracing immutability with arrays :
-> spread operator
Map, filter, slice
- In place changes are done by using these -> push(), sort(), splice().
Objects - are also mutable by default. In-place changing, same memory game.
& hence to make them immutable -> create a new instance.
-> If any change is done on an object or array, for eg. adding/removing the item from the array, then you need to create a new instance of that array or object. After some time, the old instances will be removed by the garbage collector from the memory. Whenever immutability is performed, we're sure that memory wastage/leaks are not happening as the Garbage collector keeps on removing the unused/unnecessary chunks of pieces from the memory.
Immutability in objects -> Spread operator, Object.freeze, object.assign.
Object.assign ->
If we don't use object.assign, then any change made in the new object will be reflected in the old objects & vice versa.
Strategies for change detection => default, onPush.
Default Strategy - Since the Angular app is represented via a component tree, so whenever changes are made in any component the default change detection happens for each & every component that is present in the component tree.
If we fire an event in the last level forgotPassword component then change detection will be called out for every component too. Flow - top to bottom => unidirectional always.
Every component is checked based on the updation of the data model in this case, if yes, then angular re-renders the view.
Benefits => Unidirectional approach makes sure that the view can't be updated once it's composed. So, inconsistencies with the app are avoided. To update the view, the whole change detection needs to be run again, with this approach.
Drawbacks => Time consuming, if your app is quite large then having too many components will be checked in the hierarchy again & again , hence the performance of the app is impacted.
-onPush Strategy - emphasizes Immutability; which means wherever it's used in whichever components, those components will be checked only on a certain basis i.e. when the object reference gets changed instead of the changes in data only; & hence we can avoid change detection every time. The app's performance can also be boosted accordingly.
Usage - Declare in the @Component selector => changeDetetction - changeDetetctionStrategy.OnPush
In onPush, change detection will only happen under the following circumstances -
1. Change in the input reference
In the default strategy, the change detection always runs whenever there's a change in the @Input element, but with onPush, the change detection runs only when there's a change in the reference of the Input object, i.e. when immutability is properly followed.
Avoid array.push() methods here, as the onPush Change detection won't detect any changes. instead use spread operators to create a new reference of the array.
2. Event from component or one of its children.
With onPush, every time an event is fired from a component or it's child, change detection will always run for all the components in the component tree.
But, in the case of the following scenarios, change detection won't be run.
setTimeout, setInterval, promises, and any subscription made to the observable.
3. Async pipe from the template - emitting new value.
- The async pipe internally calls markForCheck() which is responsible for the change detection triggers.
4. Manual change detection.
When to use manual change detection? => When u use onPush Strategy & you don't want to follow immutability & keep wanting to mutate your data, then comes in the picture - "MANUAL CHANGE DETECTION"!
For example - you may not want to use spread operators, or u still want to have observables in your code. But OnPush strategy won't detect any changes here, right? So, what you can do is to just invoke manual change detection.
Ways to detect manual change detection -
detetctChanges() => Create an instance of the changedetetctorRef in your constructor & call detetctChanges(). This will invoke change detection on its views & children.
ApplicationRef.tick() => triggers change detection for every component in the component tree.
markForCheck() => marks all onPush ancestors to be checked once, either on the current or next change detection cycle.
Example -
Here, add action event is being fired up from the parent component to the child, and since this is default change detection, so we'll be able to see new actions getting added to the list.
Now, let's change the change detection strategy to On push in the child component.
Upon firing the same event from the parent, still the child's view not getting rendered with the new action.
Even though, on the console, we can see that the list is getting updated with the new action.
The reason is we're using .push() method to mutate the data & not changing the reference of the input object, We're not following immutability.
Now, the changes are visible, because we've followed immutability!
If we don't want to use immutability, then we can run the ChangeDetectorRef to detect the changes.
So, on the child component, inside the ngDoCheck(), we've detected the changes and it works fine!
Why ngDoCheck()? => Since it gets called up even after the default change detection has finished its cycle. It's sort of an advanced & custom change detection!
Now, the actions are visible enough!