What I learned about Angular view creation and change detection
In the previous post, I shared some new things that I learned about the Angular compiler, and now it’s time to write something about the Angular runtime.
View Creation
According to Kara Erickson in her talk, Angular has two parts, compilation where Angular convert TS code to JS code, parses components and directives, and generates some optimized code that Angular runtime can understand to run our apps on the browsers.
We can also say that the compiler generates a component definition using the defineComponent function, and pass it to the runtime, which uses these different component definitions and run our applications.
When Angular bootstraps our applications the app.module.ts is his starting point and exactly from the component that we add to the bootstrap property.
@NgModule({
....
bootstrap: [AppComponent]
})export class AppModule { }
So he will first locate the root element, initiate the root component, and then render the root component, the last step is where Angular creates DOM elements and initiate the different directives in our apps, and this is what we can call View Creation or Creation Mode.
During the process of View, Creation Angular calls ngComponentDef for each component in the components tree starting from the root component.
After the creation of the components, Angular creates an LView array for each component instance and a TView array for each component type, both of these arrays are responsible for storing the data needed to render a component, whereas example LView can store a component instance, TView stores the component type, another example is that LView can contain binding values while TView store the property names if you want to read more details about this two arrays you can check this markdown document from the official Angular GitHub repository.
Change Detection:
We can also call it the Update Mode, it is where Angular for example checks the binding values in a specific view (template) and render it again to reflect the changes by calling the life cycle hooks, it simply syncs the view with the data model of the component.
For change detection Angular uses ZoneJs to keep an eye on each async task including event callbacks, network calls, and Timers like setTimeout, a component that is marked to be checked using ChangeDetectorRef, I also want to mention that each component in Angular has its own change detector created at the startup of the application.
ZoneJs support most of the browser Events and APIs, if a browser API is not supported by Zonejs Change detection will not fire for that API, According to a post written in Angular University Blog Zonejs does not support the indexedDB API.
Angular check for this async task starting from the bottom of the component tree to the top of it, detecting a change( by comparing the old and the new state of the component) in a component will cause this component to be marked as changed, and then updated by rerendering it.
Angular has two strategies when it comes to change detection Default and OnPush, below is an example of how we can set a change detection strategy for each component:
@Component({
selector: 'my-component',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class MyComponent { .... }
If we want to use the Default strategy there is no need to set changeDetection property in the Component Decorator.
The Default strategy
In this case, if a change detection is triggered Angular walk down the components tree and checks each change detector, and compares the old state of the component with the new one, an advantage of using this strategy is to keep simple for developers.
The problem with the default strategy is if a change is detected in a one-component this will cause the change detection to be triggered in all the other components, this will result in unnecessary change detection cycles, which will obviously cause performance issues in large applications, in this case, OnPush comes to the rescue.
The OnPush strategy
Angular tries to skip all the unnecessary checks, by treating each reference type as an immutable object, what does this mean ??, it means that Angular only compares a component property by reference and not by value. When a change detection is triggered in a specific component Angular only triggers the CD in this specific component and its descendent, the parent component updates the biding for its children and calls the lifecycle hooks.
Note that according to the discussion in this issue ngDocheck, ngContentChecked, and ngAfterViewChecked always run despite on change detection strategy.
The onPush strategy has some downsides when it comes to debugging sometimes as a developer you will have to deal with some hard and weird problems, it is also hard to understand for those junior developers who are new to Angular.
Things that a developer should not do to avoid performance issues related to change detection:
- In the example below calling getFullName() in the template will cause unnecessary change detection cycles even when person.fname and person.lname does not change
template: ```<div ngIf="person | async as p">{{getFullName(p)}}
</div>
```})
export class MyComponent { getFullName(p){ return p.lname + p.fname
}
}
- avoid using and creating impure pipes a lot in our code.
Mike Ryan in his talk Building with Ivy: rethinking reactive Angular mentioned that there is an alernative way to do Change detection in Angular by deactivating and removing Zonejs from our application and using only Ivy and observables you can read more about this here in the NgRx documentation.
In the end, it is good for a developer to know about how Angular or any other framework works behind the scenes, it helps us to understand and read the errors while debugging and developing our software, and it also gives us the ability to create more performant software.