The Angular framework was created to build rich client applications that run faster, everywhere. The Angular team keeps finding ways to make the applications written using the framework, perform better. There are several aspects that contribute to the performance of an Angular application. It includes:
- size of the bundled JavaScript files served to the browser
- the time Angular needs to render components on the page
- how the portions of the page are re-rendered when there is any change in the values
This tutorial is from the DotNetCurry(DNC) Magazine with in-depth tutorials and best practices in .NET and JavaScript. This magazine is aimed at Developers, Architects and Technical Managers and covers Angular, React.js, Vue.js, C#, Patterns, .NET Core, MVC, Azure, DevOps, ALM, TypeScript, and more. Subscribe to this magazine for FREE and receive all previous, current and upcoming editions, right in your Inbox. No Spam Policy.
Angular CLI takes care of the bundle size and eliminating unused code from the bundles, so we need not worry about the way it creates bundles. To optimize the runtime behavior and the way the components are rendered, we need to know how Angular handles the components on the browser and how Angular handles the changes happening on the objects while re-rendering the views.’'
Angular AoT – What you will learn
By the end of this Angular AoT tutorial, you will understand:
- what Angular does on the browser after loading the scripts,
- how we can optimize that process for a better runtime performance,
- the different levels of compilation in Angular,
- phases involved in compiling Angular code,
- how Angular CLI uses AoT to optimize builds and
- get an idea of setting up AoT with webpack.
Different Levels of Compilation in an Angular Application
Most of the Angular applications are written in TypeScript. The TypeScript code has to be compiled to JavaScript before loading on the browser.
If you are using Angular CLI, the built-in webpack process takes care of it. If you have your own setup for your application, you need to use a bundler with the TypeScript transpiler to compile the code and generate the bundles for deployment. Then the bundled code is loaded on the browser and Angular takes care of rendering the components starting from the root component of the application.
When Angular renders the components on the page, it converts the code into JavaScript VM-friendly code. Angular’s compiler, installed using the npm package @angular/compiler does this in the browser before the content is shown on the page. This process is called Just in Time (JiT) compilation, as the compilation happens just before the page gets rendered. The generated code makes it easier for the JavaScript VM to work with the objects in the application, thereby making change detection more efficient.
Result of the JiT compilation happening on the browser is not hidden, it is visible in the sources tab of the developer tools. The resultant code of the JiT compilation can be seen under the ng:// folder in the sources tab. The following figure shows the contents in the ng:// folder for the default application generated using Angular CLI:
Figure 1 – Contents of ng folder
As you can see, the JiT compiler produces a *.ngfactory.js file for every component and module. If you check the contents of any of these files, you will find that this code is highly annotated and adds a number of calls to Angular’s core API.
We will take a closer look at the generated code in the next section. For now, just remember that Angular runs the code inside the *.factory.js files to render the views.
A Closer Look at the Code Generated by Angular in the Browser
Let’s see how a component printing a simple Hello World message gets compiled in the browser. Say we have the following component:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `<span>{{title}}</span>
<br />
<button (click)="changeTitle()">Click here!</button>`,
styles: [`
span {
font-family: cursive
}`]
})
export class AppComponent {
title = 'Hello world!';
private count = 0;
changeTitle(){
this.count++;
this.title = `Changed! ${this.count}`;
}
}
The JiT compiled version of this component looks like the following:
Figure 2 – Contents of AppComponent.ngfactory.js
This file is created using the metadata added to the component class. It generated two objects RenderType_AppComponent and View_AppComponent_0. The module is exporting these objects, Angular will use these objects to create the component. Going into details of every single minute thing in Figure 2 is beyond the scope of this article. The vital statements of the snippets are marked with numbered comments (// 1, // 2 and so on) and they are described in the following listing:
1. Styles of the component are added to the array styles_AppComponent. As the component uses the default emulated encapsulation for the styles, selector of the span element’s style is appended with the unique attribute created for the component. This style array is used to construct the RenderType_AppComponent object.
2. The function View_AppComponent_0 converts the HTML template of the component to JavaScript. Comment 2 in Figure 2 shows how the span element is converted to JavaScript.
3. Creates the button element using JavaScript and wires up the event listeners. As the button has a click event handler attached to it in the template, we see the argument [[null, ‘click’]] and the callback function in this statement calls the functionality assigned to the (click) event in the template.
4. The callback function passed here is called whenever the component’s state is updated
5. This statement calls a method in the @angular/core library to update the span element with the new value of title
The generated code has information about all the bindings and the events added to the component’s template. As the whole HTML in the template is converted to JavaScript, and the factory provides information about every node in the template, it becomes easier to target a node and apply the update on it. This makes re-rendering of the bindings faster.
In addition to this, the AoT compiled code makes the application less prone to script injections. This is because the HTML templates are converted to JavaScript and hence the chances for someone to manipulate the templates to be rendered through scripts becomes difficult.
Phases in AoT
The AoT compilation process goes through the following phases where it analyzes the decorators, generates code based on the decorators and then validates the binding expressions.
Analysis
The analysis phase analyzes the decorators and checks if they obey rules of the compiler. If the decorators fail the rules, the compiler reports it and stops compilation.
Expression Syntax: The metadata annotations don’t support every JavaScript expression while assigning values to the metadata properties. The set of supported expressions are:
- literal objects, arrays, function calls, creation of objects using new keyword, values assigned from properties of objects or members of arrays
- simple values like strings, numbers, Boolean
- Values derived from using prefix operators (like !isValid), conditional operators, binary operators
Any other operators like increment/decrement operators would not work.
No Arrow Functions: Arrow functions can’t be used to assign values to the decorator properties. For example, the following snippet is invalid:
@Component({
providers: [{
provide: MyService, useFactory: () => getService()
}]
})
To fix this, it has to be changed as following:
function getService(){
return new MyService();
}
@Component({
providers: [{
provide: MyService, useFactory: getService
}]
})
Limited function calls: The compiler supports only a set of Angular decorators for code generation. Custom decorators won’t be used while generating code. Also, the functions used in the metadata have to be very simple and contain only a single return statement. The built-in metadata functions used in configuring routes like forRoot are defined in that way. You can check Angular’s documentation site for more details on the supported functions.
Folding: Only exported members from a module can be used in the metadata properties. Non-exported members are folded while generating the code. For example,
let selector = 'app-root';
@Component({
selector: selector
})
Gets folded into:
@Component({
selector: 'app-root'
})
But not everything is foldable. The compiler can’t fold spread operator on arrays, objects created using new keywords and function calls. You can check Angular’s documentation for the set of supported foldable expressions.
Code Generation
The compiler uses the information saved in the .metadata.json file that comes as the result of the analysis phase to generate code. While the compiler uses everything that passes analysis, it may throw errors if it finds any other semantic violations.
For example, non-exported members can’t be referenced and private class fields can’t be used in the templates. The following snippet generates an error during compilation phase:
@Component({
selector: 'app-root',
template: `<span>{{title}}</span>`
})
export class AppComponent {
private title = 'Hello world!';
}
Binding Expression Validation
In this phase, the Angular compiler checks validity of the data binding expressions in the HTML templates. The checks performed by the compiler during this phase are similar to the checks performed by TypeScript compiler on the .ts files.
The compiler checks for any typos in the template expressions and reports them in the errors. For example, consider the following snippet:
@Component({
selector: 'app-root',
template: `<span>{{title.toUpperCas()}}</span>`
})
export class AppComponent {
title = 'Hello world!';
}
Here, template of the AppComponent in the above snippet has a typo. It reports the following error:
Figure 3 – Typo error in the template
Nullable objects in the component class have to be checked before they are used in the binding expressions. The following snippet may result in an error when it is loaded on the browser:
@Component({
selector: 'app-root',
template: `<span>{{name.toUpperCase()}}</span>`
})
export class AppComponent {
name?: string;
}
Adding an ngIf to the template will prevent the error.
template: `<span *ngIf="name">{{name.toUpperCase()}}</span>`
To use an undeclared property in an object in a TypeScript file, we cast the object to any and use it. Similarly, in the template it can be type casted to any using the $any operator as shown below:
@Component({
selector: 'app-root',
template: `<span>{{$any(person).occupation}}</span>`
})
export class AppComponent2 {
person: {name: string, age: number};
constructor() {
this.person = {
name: 'Alex',
age: 10
};
this.person["occupation"] = 'Student';
}
}
The template results in an error without the $any operator.
Angular Compiler Options
The Angular compiler can be configured in the tsconfig.json file. The configuration options can be assigned using the angularCompilerOptions section. Angular provides a number of configuration options and going deep into them could be a topic for a separate article. The following listing describes some of the important options:
- skipTemplateCodegen: This option has to be used to suppress emitting ngfactory and ngstyle files. This option can be used to get the .metadata.json file generated and distribute it through npm package. This option is false by default.
- strictInjectionParameters: When set to true, it generates an error if type of the arguments passed into classed marked with @Injectable can’t be determined. This option is false by default.
- fullTemplateTypeCheck: This option tells the Angular compiler to enable the Binding Expression Validation phase. It is recommended to set this to true to avoid any undesired behavior of the application
- annotationsAs: This option enables advanced tree shaking of the Angular code. The possible values of this option are:
- decorators: It generates calls to __decorate helper classes and leaves the decorators in place. The code produced can’t be applied with advanced tree-shaking
- static fields: It replaces decorators with static fields in the class and allows advanced tree-shaking
- preserveWhitespaces: This option can be used to preserve white spaces in the HTML templates. It is set to false by default
- enableIvy: This option enables the Ivy rendered. It is the new rendered in Angular that optimizes the Angular code even further. It is still experimental, so this option is false by default
Using AoT to Build Angular Code
It is possible to generate the code that Angular generates on the browser beforehand. This process is called Ahead-of-Time (AoT) compilation. For this, the Angular CLI has all the required setup.
An application can be executed with AoT during development using the following command:
> ng serve --aot
The aot flag supplied to the ng serve command tells the Angular compiler to generate the compiled code before serving the pages to the browser. If you see the code in the file main.bundle.js served to the browser, it contains the compiled code. So, you won’t see the ng:// folder anymore, as it is not generated in the browser.
The AoT compilation also notifies of any TypeScript errors in the template. For example, if you modify the button element in the component as following,
<button (click)="changeTitle(1)">Click here!</button>`,
The AoT compiler will show a TypeScript error about the invocation of the changeTitle method. The following screenshot shows this error:
Figure 4 – TypeScript error by AoT compiler
This is quite helpful, as we get to know any potential errors during compile time itself. If an error of this type results in any odd behavior at the runtime, it becomes very hard to find the cause and fix it.
Angular CLI’s command to generate application bundle, ng build has the aot option enabled by default and the Angular CLI team hasn’t given an option to skip this option. So, every production bundle is AoT compiled code, which makes it is effective on the browser with lesser number of runtime issues.
Comparing Bundle Size and Rendering time with and without AoT
Now that we saw how Angular compiles the code on the browser and how to do it before hand, let’s see how the application performs in both cases. As first thing, let’s compare size of the bundles created in both the approaches.
The ng serve command shows the list of bundled files to be served to the browser on the console. Figure 5 shows result of the ng serve command on a default Angular CLI generated application with the AppComponent we saw earlier in this article:
Figure 5 – Size of bundles generated in ng serve command
Figure 6 shows the result when the command is supplied with the aot flag:
Figure 6 – Size of bundles generated in ng serve --aot command
On comparing size of the bundles created, you will see that main.bundle.js file is bigger when generated with the aot option. The vendor.bundle.js file is substantially smaller when generated with the aot flag.
This is because, the vendor.bundle.js file doesn’t include the Angular compiler anymore. The Angular compiler runs through the JavaScript code generated after TypeScript compilation and performs JiT compilation on the browser. This process is not required when the code is aot compiled and hence the entire Angular Compiler gets excluded from vendor.bundle.js.
This reduction in the bundle size will make the scripts download faster on the page.
The reduction in the time taken to get the page ready can be checked in the Network tab of the browsers Chrome and Opera. Figure 7 & 8 show the rendered pages and the total load time when the page is served without and with the aot option:
Figure 7 – Page load time without AoT
Figure 8 – Page load time with AoT
These figures clearly show the improvement in loading time and the size of content loaded. The page loaded files of lesser size and finished rendering the page in 1.4 seconds less than the time it took before.
Using AoT with Webpack
Though Angular CLI provides all the required setup for development of a matured Angular application, you might want to use your own custom webpack based setup.
Setting up environment for Angular development with webpack is beyond the scope of this article, we will cover the packages required for the setup and configuration for AoT here. It is possible to enable AoT on the custom webpack setup using the @ngtools/webpack package and the @angular/compiler-cli package. The following command would help with that:
> npm install -D @angular/compiler-cli @ngtools/webpack
Import this file in your webpack configuration file as shown below:
const {AngularCompilerPlugin} = require('@ngtools/webpack');
This package has a loader and a plugin that have to be used. The loader takes the TypeScript, CSS and the ngfactory files and packages them. The plugin takes a bunch of options like the path to tsconfig file, path to the application module, compiler options for AoT and others. This package has to be configured in the webpack configuration.
The following snippet configures the loader:
module: {
rules: [
{
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
loader: '@ngtools/webpack'
}
]
}
..and in the plugins section, the AngularCompilerPlugin has to be configured like it is shown here:
plugins: [
new AngularCompilerPlugin({
tsConfigPath: 'path/to/tsconfig.json',
entryModule: 'path/to/app.module#AppModule',
sourceMap: true
})
]
When to Use AoT?
AoT can be used to render the application in both development and production modes.
It is not a good option to run the application with the aot option during development, as the bundling process will need more time to generate the files to be served. So the amount of time one has to wait to see the changes on the browser would increase and it will make the developers less efficient.
Once most of the development is complete and any AoT validation issues have been fixed, then the AoT option can be used for development.
For production builds, it is always recommended to use AoT. Angular CLI imposes this and it doesn’t provide an option to turn off AoT builds for production. If you happen to create custom setup for your project, make sure to configure AoT and use it for the production builds at least to get all the advantages.
Conclusion
Angular’s compiler makes the application run better on the browser by generating JavaScript VM specific code. To make the applications run even faster, the Angular team has provided us with the option to compile the code before it loads on the browser. As we saw, the application would certainly work better on the browser with AoT enabled.
Let’s make use of this feature to make our bundles smaller and the applications faster!
This article was technically reviewed by Keerti Kotaru.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active
blogger, an author at
SitePoint and at
DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community