This post is part of my Angular Performance Tuning article series. If you looking for ways to make your Angular application faster, you might find the other articles in this series useful too.

Update: This optimization technique has now been merged into Version 4.4.0-rc.0 too.

Update 2: Beginning with Angular 6.0.0-beta.6 this optimization option is activated by default. You can deactivate it using the the configuration entries outlined below.

While performance tuning can be a challenging endeavor, there are times when progress arrives simply by upgrading to the latest release of the framework at hand. This is particularly true for Angular, where the Core Team continuously refines the internal machinery. A notable illustration is the more compact code produced by the Angular Compiler since Version 4, along with the Angular Bundle Optimizer, which rewrites code to enhance tree-shaking.

Another such optimization landed with 5.0.0-beta.4 roughly two weeks ago. This feature enables the compiler to strip unnecessary (consecutive) white spaces from text nodes and even eliminate entire text nodes that consist only of white-space characters. The outcome is a leaner output from the AOT compiler, translating into smaller bundles and quicker initial loads.

This post covers how to enable this feature, the performance gains I saw when testing it on a sample project, and the mechanics that make it work. The example application I used for these measurements is hosted in my GitHub repository.

Eliminating white spaces

Stripping white spaces from HTML is typically harmless, provided a few guidelines are followed, but it can also break your layout. Therefore, you have the option to disable it. For example, you can opt out on a per-component basis by setting the new preserveWhitespaces property to true:

@Component({
  selector: 'app-passenger-search',
  templateUrl: './passenger-search.component.html',
  styleUrls: ['./passenger-search.component.css'],
  preserveWhitespaces: true
})
export class PassengerSearchComponent  {
}

Alternatively, you can disable it across the entire application using the preserveWhitespace flag within your tsconfig.app.json:

[…]
"angularCompilerOptions": {
    "preserveWhitespaces": true
}
[…]

If you're working with the Angular CLI, be sure to place this configuration in tsconfig.app.json rather than your main tsconfig.json.

Additionally, you can shield a portion of a template by applying the ngPreserveWhitespaces attribute to any tag. For instances where you need to retain a specific white space that might otherwise be removed, the pseudo-entity &ngsp; can be used; it gets converted into an actual space in the generated code. This should not be mixed up with the more familiar entity.

Outcomes from the sample project

After applying this optimization to my example application, here are the figures I recorded:

  • ~ 8% reduction in size for the javascript bundle
  • ~ 4% reduction in size for the gzipped javascript bundle
  • ~ 9% faster application startup time

As these numbers indicate, this method yields a significant performance boost. The improvement from simply removing white spaces is more substantial than one might initially anticipate. The next section details why that is the case.

Examining the internals

To grasp why stripping white spaces alone leads to such a noticeable performance increase, let's inspect a basic template:

<h1>
  Search for Passengers 
</h1>
<p>
  Lorem ipsum dolor sit amet.
</p>

When compiled, the AOT compiler turns this template into JavaScript:

function View_PassengerSearchComponent_0(_l) {
  return __WEBPACK_IMPORTED_MODULE_1__angular_core__["_37" /* ɵvid */](
      0,
      [
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_15" /* ɵeld */](
                     0, null, null, 1, 'h1', [], null, null, null, null, null)),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_35" /* ɵted */](
                     null, [ '\n  Search for Passenger \n' ])),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_35" /* ɵted */](
                     null, [ '\n' ])),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_15" /* ɵeld */](
                     0, null, null, 1, 'p', [], null, null, null, null, null)),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_35" /* ɵted */](
                     null, [ '\n  Lorem ipsum dolor sit amet.\n' ]))
      ],
      null, null);
}

Notice that the generated code includes a function call for every DOM node present. For instance, there's a call for the h1 tag and another for the p tag. Additionally, there are calls for each text node. Upon closer inspection, you'll find characters and even whole text nodes that the browser doesn't render because of how HTML handles whitespace. These include consecutive spaces and empty text nodes, such as the one located between the closing h1 tag and the opening p tag.

With the optimization described here, the compiler eliminates these superfluous white spaces and text nodes. The following image highlights the differences between the original emitted code and the version produced once white-space removal is activated:

diff between version with and w/o whitespaces

This reveals that an entire function call along with some consecutive white space characters are removed. If we estimate by merely counting the deleted lines, they represent roughly 10 % of the total lines. This accounts for the bundle size reduction we observed. Naturally, fewer function calls also mean the browser can download the bundles more quickly, and Angular has less work to do during startup. Both factors contribute to the faster initial load times discussed earlier.