Angular ~ Optimization & Performance - rohit120582sharma/Documentation GitHub Wiki
- Optimization
- Minification
- Uglification
- Bundling
- Dead code elimination
- Ahead-of-time (AOT) Compilation
- Application Performance
- Change Detection
- Use
trackBy
option for*ngFor
directive - Remove subscription in
ngOnDestroy
life-cycle hook - Using Lazy Loading
- Other techniques
- Angular Universal
- Web Workers
- Service Workers
- References
Every Angular application involves a compilation step to compile the template code into JavaScript code.
When our application starts, Angular compiler is going to kick in and to walk down the tree of components. It is going to parse the template of each component and produce the JavaScript code to create the same structure in the DOM.
Angular offers two types of compiling the code.
It is default compilation.
We develope our code. Next, we ship the code in production which can be served in the browser. Next, we view it in a browser, so the app (whole source code) gets downloaed. Next, Angular bootstraps the application. In this step, it parses and compiles all templates (to JavaScript).
We develop our code. Next, we allow angular to parse and compile all templates (to JavaScript) ahead of time. In other words, allow angular to understand our templates at an earlier point of time. Next, we ship the code in production. Next, we view it in a browser, so the app (pre-compiled code) gets downloaded.
Faster Startup since Parsing and Compilation doen't happen in browser.
Templates get checked during development.
Smaller File Size as unused features can be stripped out and the compiler itself isn't shipped.
More secured, because JIT uses eval.
Need to run ng build
command with some flags. The --prod
flag tries to optimize and minify the code. The --aot
flag uses AOT compilation. This command creates dist
folder as production build.
ng build --prod --aot
Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import
and export
.
For tree shaking use ng build --prod --build-optimizer
. This way vendor.js and main.js are combined in main.js file. To avoid that you have to add --vendor-chunk=true
.
Zone.js
provides a mechanism, called zones, for encapsulating and intercepting asynchronous activities in the browser (e.g. setTimeout, , promises). Zone.js
monkey patches all asynchronous APIs in the browser and triggers the change detection in the end of the execution of any async callback.
These zones are execution contexts that allow Angular to track the start and completion of asynchronous activities and perform tasks as required (e.g. change detection). Zone.js provides a global zone that can be forked and extended to further encapsulate/isolate asynchronous behavior, which Angular does so in its NgZone
service, by creating a fork and extending it with its own behaviors.
The NgZone service provides us with a number of Observables and methods for determining the state of Angular's zone and to execute code in different ways inside and outside Angular's zone.
When you change any of your models, Angular detects the changes and immediately updates the views. This is change detection in Angular. The purpose of this mechanism is to make sure the underlying views are always in sync with their corresponding models.
A model in Angular can change as a result of any of the following scenarios:
- DOM events (click, hover over, etc.)
- AJAX requests
- Timers (setTimer(), setInterval())
All Angular apps are made up of a hierarchical tree of components. At runtime, Angular creates a separate change detector class for every component in the tree, which then eventually forms a hierarchy of change detectors similar to the hierarchy tree of components.
Whenever change detection is triggered, Angular walks down over the entire tree of change detectors to determine if any of them have reported changes. The change detectors provide a way to keep track of the component’s previous and current states as well as its structure in order to report changes to Angular. If Angular gets the report from a change detector, it instructs the corresponding component to re-render and update the DOM accordingly.
The change detection cycle is always performed once for every detected change and starts from the root change detector and goes all the way down in a sequential fashion. This sequential design choice is nice because it updates the model in a predictable way since we know component data can only come from its parent.
A way to improve the performance of the change detection is to not perform it for subtrees which are not supposed to be changed based on the recent actions.
The Default
change detection strategy check for any differences between the previous state and current state of the overall application model. The question that Angular asks in the default change detection strategy is: Has any value in the model changed?
The OnPush
change detection strategy allows us to disable the change detection mechanism for subtrees of the component tree. By setting the change detection strategy to any component to the value ChangeDetectionStrategy.OnPush
, will make the change detection perform only when the component have received different inputs. Angular will consider inputs as different when it compares them with the previous inputs by reference, and the result of the reference check is false. In combination with immutable data structures OnPush
can bring great performance implications for such "pure" components.
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
// ...
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnPushComponent {
// ...
}
Another way of implementing a custom change detection mechanism is by detaching and reattaching the change detector (CD) for given component. Once we detach
the CD Angular will not perform check for the entire component subtree.
@Component({
selector: 'giant-list',
template: `
<li *ngFor="let d of dataProvider.data">Data {{d}}</lig>
`,
})
class GiantList {
constructor(private ref: ChangeDetectorRef, private dataProvider: DataProvider) {
ref.detach();
setInterval(() => {
this.ref.detectChanges();
}, 5000);
}
}
In rare cases we may want given code to be executed outside the context of the Angular Zone and thus, without running change detection mechanism. In such cases we can use the method runOutsideAngular
of the NgZone
instance.
@Component({
selector: 'giant-list',
template: `
<li *ngFor="let d of dataProvider.data">Data {{d}}</lig>
`,
})
class GiantList {
constructor(private ref: ChangeDetectorRef, private _zone: NgZone) {}
ngOnChanges(changes: any) {
this._ngZone.runOutsideAngular(() => {
// change something
});
}
}
The *ngFor
directive is used for rendering a collection. By default *ngFor
identifies object uniqueness by reference.
If, at some point, we need to change the data in the collection, we will be faced with a problem, because Angular can’t keep track of items in the collection and has no knowledge of which items have been removed or added. As a result, Angular needs to remove all the DOM elements associated with the data and create them again. This can mean a lot of DOM manipulations, especially in the case of a big collection. And, as we know, DOM manipulations are expensive.
Developer can provide a hint for angular how to identify object uniqueness: custom tracking function as the trackBy
option for the *ngFor
directive. Tracking function takes two arguments: index
and item
. Angular uses the value returned from tracking function to track items identity.
@Component({
selector: 'my-app',
template: `
<li *ngFor="let item of list; trackBy:identify">{{item.name}}</li>
`
})
export class App {
list:[];
identify(index, item){
return item.name;
}
By default, Webpack will output all your app’s code into one large bundle. Lazy loading gives you the ability to optimize your application load time by splitting the application to feature modules and loading them on-demand.