What's new in Angular CLI 7.3?

Angular CLI 7.3.0 is out! For once, there is no Angular release at the same time. The next version for the framework should be 8.0.0 in a few months.

If you want to upgrade to 7.3.0 without pain (or to any other version, by the way), I have created a Github project to help: angular-cli-diff. Choose the version you’re currently using (6.2.1 for example), and the target version (7.3.0 for example), and it gives you a diff of all files created by the CLI: angular-cli-diff/compare/6.2.1…7.3.0. It can be a great help along the official ng update @angular/core @angular/cli command. You have no excuse for staying behind anymore!

Let’s see what we’ve got in this release!

Conditional ES5 browser polyfill loading

If you target older browsers, you used to need to uncomment a few things in the polyfills.ts file. For example, if you want to target IE 9, 10 and 11, a few polyfills from core-js are needed. These polyfills allow to use modern JS features (that Angular needs) even in older browsers, and were commented in the dedicated polyfills.ts file, where you could remove the comments if you wanted to include them.

But since Angular CLI 7.3, you don’t have to do this anymore: it’s done for you automatically! The CLI generates a bundle containing all the polyfills needed for older browsers called es2015-polyfills.***.js and adds it in the index.html.

As adding these polyfills makes the application heavier, you might be wondering if this a good idea. But there a twist: the CLI adds the script in index.html with a nomodule attribute. This attribute indicates to modern browsers (that supports ECMAScript modules) to ignore this script, so it’s not even fetched on modern browsers! If you ever tried Vue CLI, this is very close to the “modern” build mode.

This new behavior is controlled via the es5BrowserSupport option in angular.json. It saves roughly 56Kb (19Kb compressed) on modern browsers, so that’s totally worth it. You can activate it by adding the option in your configuration, and update your polyfills.ts file (check angular-cli-diff/compare/7.2.0…7.3.0 to see how).

Guard schematics for all interfaces

In previous version of the CLI, when using the schematic to generate a new router guard, you ended up with a CanActivateGuard (which is probably the most used one, but not the only one).

In CLI 7.3, when you run the same schematic, you now have a multi-select choice looking like:

ng g guard logged-in
? Which interfaces would you like to implement? (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ CanActivate
◯ CanActivateChild
◯ CanLoad

You can choose one or more interfaces to implement, and of course the result will be a guard implementing these interfaces. Note that this multi-select question in interactive mode is also a new feature of the CLI. The cool thing is that you can also directly specify the interfaces you want:

ng g guard logged-in --implements CanActivate

TSLint extends tslint:recommended

This is a feature I really like because I added it 😉.

The default TSLint configuration now extends tslint:recommended, the recommended set of rules of the TSLint team.

While updating the TSLint configuration in the CLI, we also activated a few more rules. You can see the difference here.

Try it in your projects, you may catch a few hidden bugs (or at least make your code prettier)!

Note that the CLI now also officially supports the TSLint configuration file to be written in YAML (the default generated one is in JSON).

Catch errors in your e2e tests

A slight change has been applied to the default e2e test generated:

afterEach(async () => {
  // Assert that there are no errors emitted from the browser
  const logs = await browser.manage().logs().get(logging.Type.BROWSER);
  expect(logs).not.toContain(jasmine.objectContaining({
    level: logging.Level.SEVERE
  } as logging.Entry));
});

This afterEach function will run after each one of your tests (obviously) and will report eventual runtime errors. This can be really useful to catch hidden errors in your e2e tests.

That’s all for this release, I hope you enjoyed reading this blog post. Stay tuned!

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!


What's new in Angular CLI 7.2?

Angular CLI 7.2.0 is out!

If you want to upgrade to 7.2.0 without pain (or to any other version, by the way), I have created a Github project to help: angular-cli-diff. Choose the version you’re currently using (6.2.1 for example), and the target version (7.2.0 for example), and it gives you a diff of all files created by the CLI: angular-cli-diff/compare/6.2.1…7.2.0. It can be a great help along the official ng update @angular/core @angular/cli command. You have no excuse for staying behind anymore!

Let’s see what we’ve got!

TypeScript 3.2 support

As Angular 7.2 now supports TypeScript 3.2 (check out our article about Angular 7.2), the CLI officially supports it too.

New flag and flag values for ng build

new resourcesOutputPath option

It is now possible to specify where resources will be placed, relative to outputPath. In short you can ouput your CSS in other instead of the root of the output folder with:

ng build --resources-output-path=other

sourceMap fine-grained values

The sourceMap flag of ng build used to take a boolean value: true to generate the source maps for styles and scripts, false to ignore them.

It can now take a more fine-grained value, as you can now give an object to configure if you want only the scripts source maps, the styles source maps, the vendor source maps, or the hidden source maps, a new feature to generate this special kind of source maps that some error reporting tools like Sentry use.

"sourceMap": {
  "scripts": true,
  "styles": true,
  "hidden": true,
  "vendor": true
}

The vendor option replaces the now deprecated vendorSourceMap flag.

optimization fine-grained values

Similarly, the optimization flag used to take a boolean value: true to activate scripts and styles optimization, false to ignore them (leading to faster builds).

Now you can activate one or the other, as optimization can now take an object to configure it:

"optimization": {
  "scripts": true,
  "styles": true
}

Build Event Log

A new flag called buildEventLog has been added to ng build. This flag needs a filename as a parameter and will generate the specified file, with a timeline of the build events. Currently, running ng build --build-event-log log.txt generates:

{"id":{"started":{}},"started":{"command":"build","start_time_millis":1544793799294}}
{"id":{"finished":{}},"finished":{"finish_time_millis":1544793807037,"exit_code":{"code":0}}}

As you can see, it only logs a started and finished events for now. This is heavily inspired by the Build Event Protocol, and we will probably get a more detailed report in the future (this is probably introduced with the future Bazel build in mind).

New flag for ng update

A new verbose flag is available for this command to detail what exactly is going on and help debug potential issues.

Flag deprecations

The CLI added the possibility to deprecate a flag in a schematic (with the x-deprecated field in a schema).

The team took the occasion to deprecate a few flags from the CLI itself:

  • evalSourceMap in ng build/serve as it could be used to improve build performances, but isn’t needed anymore.
  • vendorSourceMap in ng build/serve, as it has been replaced, see above.
  • skipAppShell in ng build/serve for a Web application as it had no effect.
  • styleext in ng generate, and should now be style. It is used to specify the extension of the style file of a new component (for example, ng generate component user --style scss).
  • spec in ng generate, and should now be skipTests. It is used to not generate tests (for example, ng generate component user --skip-tests).

Flag override warning

Note that you will now have a warning when you specify a flag several times in the same command. It used to pick the last one silently, and now it will still do the same and also warn you that there is something weird going on:

> ng build --aot --aot=false
Option aot was already specified with value true. The new value false will override it.

But it doesn’t warn you if you use: ng build --prod --aot=false.

That’s all for this small release, I hope you enjoyed reading this blog post. Stay tuned!

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!


What's new in Angular 7.2?

Angular 7.2.0 is here!

Angular logo

Not a lot of new features in this release: the Angular team is still mainly focused on the Ivy project, rewriting the Angular compiler and runtime code to make it smaller, better, faster.

TypeScript 3.2 support

One of the new features is the support of TypeScript 3.2, which is the latest release! You can check out what was introduced in TypeScript 3.2 on the Microsoft blog.

ng new with Bazel support!

I was mentioning in our article about Angular 7.1 that the Bazel support was making progress, and this 7.2 release brings a cool new feature. It’s more a new CLI feature and maybe should be mentioned in my upcoming blog post about “What’s new in Angular CLI 7.2”, but the code of @angular/bazel lives in the Angular repo and not in the CLI repo.

This new feature is the possibility to generate a new project with Bazel build! 🚀

npm i -g @angular/bazel
ng new my-app --collection=@angular/bazel --defaults

And boom! You have a new project that uses Bazel.

You can then run the usual commands like ng serve/build/test, and they will not use the default CLI builders, but the Bazel ones. Note however that all the usual flags and options of the CLI are not supported yet.

This is still very experimental and early stage, so you will probably encounter various issues if you give it a try.

Router

History state

The router gains a new feature allowing to pass dynamic data to the component you want to navigate to, without adding them into the URL. This could be done by using a shared service, but it can be cumbersome to have to create a service to just pass a few data.

The router now uses the full capacity of the Browser History API, and allows to pass a state property to NavigationExtras, the options that you can pass to some router methods, like navigateByUrl.

this.router.navigateByUrl('/user', { state: { orderId: 1234 } });

or in a link, thanks to a new state input:

<a [routerLink]="/user" [state]="{ orderId: 1234 }">Go to user's detail</a>

The state will be persisted to the browser’s History.state property. Later, the value can be read from the router by using getCurrentNavigation:

const navigation = this.router.getCurrentNavigation();
this.orderId = navigation.extras.state ? navigation.extras.state.orderId : 0;

New options for runGuardAndResolvers

This is not a left over copy/paste from the 7.1 article: there are other new options for runGuardsAndResolvers in 7.2!

runGuardsAndResolvers is one of the configuration options for a route, allowing to define when the guards and the resolvers will be run for this route. By default, they run only when the path or matrix parameters change (value paramsChange). You can override this behavior by using another value for this option like paramsOrQueryParamsChange, to also trigger the guards and resolvers if a query parameter changes, or always to trigger them if anything changes. Angular 7.1 introduced a new possible value: pathParamsChange. Using this value, the guards and resolvers will run if a path parameter changes, but not if a query or matrix parameter changes. Angular 7.2 introduces another option: pathParamsOrQueryParamsChange. using this value, the guards and resolvers will run if a path parameter or a query parameter changes, but not if a matrix parameter changes.

I think a configuration object with boolean switches for path, query or matrix parameters would make things clearer at that point 😅.

But another new possibility in 7.2 is to define your own predicate function to indicate to runGuardsAndResolvers if it should run the guards and resolvers. The function should look like (from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean, for example:

runGuardsAndResolvers: (from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot)
  => to.paramMap.get('query') !== from.paramMap.get('query')

In that case, the guards and resolvers will only run if the query param changed.

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!


What's new in Angular CLI 7.1?

Angular CLI 7.1.0 is out!

If you want to upgrade to 7.1.0 without pain (or to any other version, by the way), I have created a Github project to help: angular-cli-diff. Choose the version you’re currently using (6.2.1 for example), and the target version (7.1.0 for example), and it gives you a diff of all files created by the CLI: angular-cli-diff/compare/6.2.1…7.1.0. It can be a great help along the official ng update @angular/core @angular/cli command. You have no excuse for staying behind anymore!

Let’s see what we’ve got!

Package manager auto detection

The CLI should now do a better job to detect the package manager you use (NPM or Yarn), and use it for the various commands like ng update or ng add.

tslib as a dependency

The CLI applications have a new required dependency: tslib. This Microsoft library contains TypeScript helpers. The CLI now uses one of them to avoid repeating code for every class regarding imports. The use of these helpers is activated by an option importHelpers in the tsconfig.json file:

"experimentalDecorators": true,
"importHelpers": true,

By including these helpers, and avoiding to repeat the same code over and over, the sizes of your bundles should be slightly reduced (don’t expect miracles though).

@angular/http removed

@angular/http has been deprecated for a long time in favor of @angular/common/http, and it is now removed from the generated package.json file.

That’s all for this very small release, I hope you enjoyed reading this blog post. Stay tuned!

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!


What's new in Angular 7.1?

Angular 7.1.0 is here!

Angular logo

Not a lot of new features in this release: the Angular team is still mainly focused on the Ivy project, rewriting the Angular compiler and runtime code to make it smaller, better, faster.

Beginning of Bazel support

A new @angular/bazel package appeared in the Angular repository, containing the stepping stones for building our Angular applications with Bazel. It also contains a schematics collection (with a target currently called bazel-workspace) to generate the necessary files in an Angular CLI application.

npm i -g @angular/bazel
ng generate @angular/bazel:bazel-workspace my-app // adds the Bazel build files to a CLI project

I guess that in a near future, we should be able to directly generate a CLI app with Bazel build (something like ng new my-app --collection=@angular/bazel).

The bazel-workspace target already allows to build, serve, test and launch the e2e tests with Protractor. This is still quite experimental.

Note that Bazel is now published on NPM directly, removing the need to install it manually.

Router

CanActivate guard can return a UrlTree

The signature of CanActivate changed and it now can return Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree instead of Observable<boolean>|Promise<boolean>|boolean. It means that whereas previously you would have returned a boolean (or something that yields a boolean later), you can now return the URL where you want to redirect your user.

So you can write:

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
  return this.userService.isLoggedIn() || this.router.parseUrl('/login');
}

instead of:

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
  const loggedIn = this.userService.isLoggedIn();
  if (!loggedIn) {
    this.router.navigateByUrl('/');
  }
  return loggedIn;
}

The difference is also that now the router will behave correctly if several guards trigger different redirects (it was not the case before, and that could lead to non deterministic behavior in redirections). Note that you can also use router.createUrlTree() to build a UrlTree with parameters.

New option for runGuardAndResolvers

runGuardsAndResolvers is one of the configuration options for a route, allowing to define when the guards and the resolvers will be run for this route. By default, they run only when the path or matrix parameters change (value paramsChange). You can override this behavior by using another value for this option like paramsOrQueryParamsChange, to also trigger the guards and resolvers if a query parameter changes, or always to trigger them if anything changes.

Angular 7.1 introduces a new possible value: pathParamsChange. Using this value, the guards and resolvers will run if a path parameter changes, but not if a query or matrix parameter changes.

Forms

updateOn in FormBuilder

The updateOn option is available since Angular 5, but it was only usable if you used the constructor of FormGroup directly:

this.userForm = new FormGroup({
  username: '',
  password: ''
}, {
  validators: Validators.required,
  updateOn: 'blur'
});

It is now possible to use it via the group helper method of the FormBuilder. Note that this updated group method can now take an AbstractControlOptions as the second parameter, allowing to have a more coherent API, and use exactly the same syntax as in FormGroup:

this.userForm = fb.group({
  username: '',
  password: ''
}, {
  validators: Validators.required,
  updateOn: 'blur'
});

The old form of options is now deprecated (it was using validator and asyncValidator instead of validators and asyncValidators).

Service Worker

It’s now possible to be notified when a user clicks on a push notification, via the notificationClicks observable on the service SwPush.

Ivy update

The Ivy rewrite is still in progress, but I noted that I missed a nice addition: there will be public discovery utils that can be used to debug your application in the browser. Several functions will be available in the browser console: getComponent(target), getDirectives(target), getHostComponent(target), getInjector(target), getRootComponents(target), and getPlayers(target).

In Chrome for example, you can inspect an element and that will store the current element in a variable called $0. Then in the browser console, you can do ng.getComponent($0) and it will return the component associated to the element! You can check out Jason Aden talk at AngularConnect for more info on the topic.

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!


What's new in Angular CLI 7.0?

Angular CLI 7.0.0 is out (in fact we even have a 7.0.1 available)!

If you want to upgrade to 7.0.1 without pain (or to any other version, by the way), I have created a Github project to help: angular-cli-diff. Choose the version you’re currently using (6.2.1 for example), and the target version (7.0.1 for example), and it gives you a diff of all files created by the CLI: angular-cli-diff/compare/6.2.1…7.0.1. It can be a great help along the official ng update @angular/core @angular/cli command. You have no excuse for staying behind anymore!

Let’s see what we’ve got!

Interactive prompts

One of the major additions to this version: the CLI now offers interactive prompts on several commands to let the developer choose some options or names.

For example, if you run ng new with CLI 7.0, then it asks you:

? What name would you like to use for the project?
? Would you like to add Angular routing? y/N
? Which stylesheet format would you like to use? (Use arrow keys)
❯ CSS
  SCSS   [ http://sass-lang.com   ]
  SASS   [ http://sass-lang.com   ]
  LESS   [ http://lesscss.org     ]
  Stylus [ http://stylus-lang.com ]

You can enter the name of the project, choose to add or not the routing support (with No as a default value if you just press Enter), and pick which CSS pre-processor you want, allowing to choose between CSS, SCSS, SASS, LESS and Stylus by using the arrow keys.

Note that all these options were already available, but you had to know the correct flags to add. For example ng new ponyracer --routing --style=scss.

If you don’t specify one of the options, a prompt will appear. You can of course deactivate this interactive mode, by using ng new ponyracer --no-interactive (no prompt at all), or ng new ponyracer --defaults (uses the default option of the prompt if it exists).

All the ng generate commands (component, service, pipe, etc.) will also ask for the name of the entity to generate if not provided.

You can add these prompts to your own schematics too. See how easy it is to add, for example in ng new.

Fun with flags!

A lot of flags have been added to various commands!

ng serve

A --verbose flag is now available for ng serve and ng build, displaying how much time each task took, how much each asset weighs, etc.

ng build

ng build now has a --profile flag.

It outputs two files:

  • chrome-profiler-events.json
  • speed-measure-plugin.json

The first one is a Chrome profile file, that you can load via the Performances tab in your Chrome Dev Tools. The second one is the result of the Speed Measure Webpack plugin and contains information about how much time each plugin took.

This is not really intended for us, but to help the CLI team improving some projects with long build times. If you are in this case, you can now offer proper information to the team about your build, and they might be able to speed things up in future releases.

The CLI README now also has a dedicated section to CPU profiling of your build.

ng generate

ng generate component now accepts --viewEncapsulation=ShadowDom to reflect the new view encapsulation option added in Angular 6.1.

ng new

We already talked about --no-interactive and --defaults, but ng new also earned a flag called --no-create-application. If you use it, the CLI will create a workspace with the NPM, TypeScript, TSLint and Angular CLI configurations, but with no application (so no src and e2e directories).

Along the same lines, a new flag called --minimal will generate a workspace with a project, but with the bare minimum: no unit tests or e2e tests, no TSLint either, and it uses inline styles and templates in components. This can be useful if you just want to setup a repository for a quick proof of concept.

ng test

The --reporters flag for the test command is back, after disappearing for a few versions. It allows to directly specify which reporters you want Karma to use, so it can be useful on a CI for example.

ng xi18n

You can now turn off the progress of the build when extracting the i18n messages with: ng xi18n --no-progress.

TypeScript 3.1 support

As Angular 7.0 now requires TypeScript 3.1 (check out our article about Angular 7), the CLI officially supports it too. This also includes a few optimizations in build-optimizer specific to Angular 7.0/TS 3.1.

Terser instead of UglifyJS

As uglify-es is no longer maintained and uglify-js does not support ES6+, the CLI team has moved to Terser for the minification phase of the build. Terser is a fork of uglify-es that retains API and CLI compatibility with uglify-es and uglify-js@3. It shouldn’t really change the results, but it fixes a few long standing issues with UglifyJS, like production builds that weren’t working in old Firefox ESR versions.

Configuration

In angular.json, you can now ignore certain files in your assets, with the brand new ignore option:

"assets": [
  {
    "glob": "**/*",
    "input": "src/assets/",
    "ignore": ["**/*.svg"],
    "output": "/assets/"
  },
],

On the polyfill side, the reflect-metadata polyfill (core-js/es7/reflect) is now only included in JiT mode, as it is not needed in AoT (production) mode. If you run ng update to update to 7.0, it should be automatically removed and your bundle will be a few kB lighter!

Talking about bundle sizes, a new application now has some “budgets” set by default:

budgets: [{
  type: 'initial',
  maximumWarning: '2mb',
  maximumError: '5mb',
}],

When you build your application, you’ll see a warning if the bundle is over 2MB, and an error if it is over 5MB. You can customize these limits of course (see our article about the CLI 1.7, the version that introduced budgets).

Performances

The CLI team released a new package (still experimental), called benchmark. The goal is to help benchmarking a NodeJS process, by measuring the time, CPU usage, memory usage, etc. So it’s not specific to the CLI itself. You can check out the README to learn more. The CLI team probably intends to track the performances of the various tools they are currently releasing, but maybe you can use it on your projects too.

.npmrc per project

You can now define one .npmrc file per project in your workspace, making it easier to deploy artefacts to your Nexus or Artifactory repository.

Breaking change

This is a small one, but worth noting: the CLI no longer inlines the assets less than 10kb in the CSS. If you had a small image, it used to be inlined directly in the generated CSS.

Eject is not coming back

As you may know, the CLI used to have an eject command, making it possible to customize the Webpack config directly (at the price of losing the CLI support). It was temporarily removed in CLI 6.0 due to the internal refactoring, but it will not come back. It will be removed completely in 8.0. The team thinks that the new configuration format provides enough flexibility to modify the configuration of your workspace without ejecting. They also mention ngx-build-plus if you want even more customization without ejecting.

That’s all for this release, I hope you enjoyed reading this blog post. Stay tuned!

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!


What's new in Angular 7?

Angular 7.0.0 is here!

Angular logo

Not a lot of new features in this release: the Angular team is mainly focused on the Ivy project, rewriting the Angular compiler and runtime code to make it smaller, better, faster. But Ivy is not ready for prime time yet.

So don’t expect a lot of shiny things in Angular 7.0: there was not enough material to make a video as we did for Angular 5 or Angular 6. This will be a short blog post for once, and a fast and easy upgrade for you!

TypeScript 3.1 support

One of the main new features is the support of TypeScript 3.1, which is the latest release! It is in fact mandatory to bump to TS 3.1 for Angular 7. Usually Angular lags a few releases behind, so it’s great to be able to use the latest TypeScript version for once! You can check out what was introduced in TypeScript 3.0 and TypeScript 3.1 on the Microsoft blog.

Angular compilation options

As you may know, you can define compilerOptions for TypeScript and angularCompilerOptions for Angular in your tsconfig.json files:

{
  "extends": "../tsconfig.base.json",
  "compilerOptions": {
    "experimentalDecorators": true,
    // ...
  },
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "preserveWhitespaces": true,
    // ...
  }
}

TypeScript allows you to extend the compilerOptions of another file (see the extends part in the example), but it was not doing anything with the Angular compiler options. The Angular compiler is now fixed, and you can define Angular compiler options in a base config, then extend them in another file. The options will be merged with the one defined in the inheriting config file, as they are for the TypeScript compiler options!

Ivy progress

The rewrite is making progress, but Ivy is still not usable in this release. If you refer to the official feature tracking, a good chunk of the work is done. But in reality, there is still a long way to go before we can use it.

Ivy has several pieces:

  • ngtsc: the compiler that compiles your Angular application and generates JavaScript from your HTML templates. This piece of code has made good progress but still misses a few features.
  • ngcc: a tool that explores all the dependencies you have, to convert existing code into “Ivy compatible” code. This is still very early stage, and barely usable at the moment if you don’t know how to workaround a few issues (Olivier and Pete from the Angular team were nice enough to help me).
  • the renderer itself, which takes the generated code and makes the magic happen at runtime. It still moves a lot, as new optimizations and new issues are found by the team.

Another part that is eagerly awaited is the support of “runtime i18n”. The implementation work has just started this week, so this is also far from being done.

I gave Ivy a few shots lately but it is definitely not ready for prime time yet. Internally at Google, the Angular team needs to migrate the huge number of projects they have to gather feedback and fix issues. So it will take a few more months.

But you can check it out yourself, as the CLI added an --experimental-ivy flag to generate an application with the configuration needed to try it.

ng new ivy-test --experimental-ivy
cd ivy-test
$(npm bin)/ngcc
ng serve --aot

Note that the change detection is not working as I’m writing these lines, so this is very limited right now :).

Slots with Angular Elements

It is possible to use ViewEncapsulation.ShadowDom since Angular 6.1, which is great for Angular Elements (Angular components packaged as Web components that you can use alone). But there was a missing feature to be able to use <slot>, a new standard HTML element, introduced by the Web Component specification. This feature is now available, enabling components with a template like:

@Component({
  selector: 'ns-card',
  template: `
    <header>
      <slot name="card-header"></slot>
    </header>
    <slot></slot>`,
  encapsulation: ViewEncapsulation.ShadowDom,
  styles: []
})
export class CardComponent {
}

That can later be used as an Angular Element like this:

<ns-card>
  <span slot="card-header">Become a ninja with Angular</span>
  <p>A wonderful book from Ninja Squad</p>
</ns-card>

Router

A new warning has been added if you try to trigger a navigation outside of the Angular zone, As it doesn’t work if you do so, Angular now logs a warning (only in development mode). This is pretty rare but can happen for example if you try to redirect your users when an error occurs in the application by providing a custom ErrorHandler (as the handleError method will run outside the ngZone to avoid a potential infinite loop). The warning looks like:

Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?

Sadly, this introduces warnings in your unit tests if you use the router in some of them, and looks like it’s an issue in Angular itself (see this issue if you want to add a thumb up).

Another internal work that we can’t really see has been the rewrite of the router to use a single Observable under the hood, that will automatically cancel the previous navigations. It will not affect you, but should fix a bunch of issues when multiple navigations were triggered at the same time and will allow new features more easily in the future.

Deprecations

As it’s usually the case with major releases, a few things have been deprecated. If you are using <ngForm> to declare a form in your template (you don’t have to, as form also activates the NgForm directive), this selector is now deprecated and should be replaced by <ng-form>.

As you can see, the release contains very few interesting features, but Ivy is making progress and Angular 8.0 will probably have more cool stuff!

In the meantime, the upgrade of your applications should be very easy.

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!


Angular Performances Part 5 - Pure pipes, attribute decorator and other tips

This is the last part of this series (check the first part, the second one, the third one and the fourth one if you missed them), and this blog post is about how you can improve the runtime performances of your Angular application with pure pipes, the attribute decorator and other tips. If you are the lucky owner of our ebook, you can already check the other parts if you download the last ebook release.

Now that we have talked about first load, reload, profiling and change detection strategies we can continue our exploration of the tips for better runtime performances.

Pure pipes

As you know, you can build your own pipes to format and display your data. For example, to display the full name of a user, you can either write a method in your component:

@Component({
  selector: 'ns-menu',
  template: `
      <p>{{ userName() }}</p>
      <p>...</p>
      <p>{{ userName() }}</p>
  `
})
export class MenuComponent {

  user: UserModel = {
    id: 1001,
    firstName: 'Jane',
    lastName: 'Doe',
    title: 'Miss',
  };

  userName() {
    return `${this.user.title}. ${this.user.firstName} ${this.user.lastName}`;
  }
}

or write a custom pipe to encapsulate this logic:

@Component({
  selector: 'ns-menu',
  template: `
      <p>{{ user | displayName }}</p>
      <p>...</p>
      <p>{{ user | displayName }}</p>
  `
})
export class MenuComponent {

  user: UserModel = {
    id: 1001,
    firstName: 'Jane',
    lastName: 'Doe',
    title: 'Miss',
  };
}

with DisplayNamePipe looking like:

@Pipe({
  name: 'displayName'
})
export class DisplayNamePipe implements PipeTransform {

  transform(user: UserModel): string {
    return `${user.title} ${user.firstName} ${user.lastName}`;
  }

}

This takes a little bit more work, but writing a pipe allows to reuse it in other components.

What you may not know is that using a pipe is also more performant. By default, a pipe is “pure”. In computer science, we call “pure” a function that has no side effect, and whose result only depends on its entries. A pure pipe is pretty much the same: the result of its transform method only depends on arguments. Knowing that, Angular applies a nice optimization: the tranform method is only called if the reference of the value it transforms changes or if one of the other arguments changes (yes, a bit like the OnPush strategy for components).

It means that whereas a method of a component is called on every change detection, a pure pipe will only be executed when needed, and only once in a template if it is used with the same input value and arguments (as in my example).

By default, a custom pipe is pure, so that’s great! But sometimes it’s not a right fit.

In my example, if we mutate the user to set its firstName to a different value, the pipe never refreshes… It’s pretty much the same issue that we had with the OnPush strategy: the reference of the value doesn’t change, so the pipe does not run again.

Here you have two solutions:

  • carefully use the pipe with immutable objects (do not mutate the user, create a new user with the new firstName);
  • mark the pipe as “impure”, and Angular will run it every time. You lose a tiny bit in performance, but you are sure that the displayed value is refreshed.

To mark a pipe as impure, just add pure: false in its decorator:

@Pipe({
  name: 'displayName',
  pure: false
})
export class DisplayNameImpurePipe implements PipeTransform {

  transform(user: UserModel): string {
    return `${user.title} ${user.firstName} ${user.lastName}`;
  }

}

To sum up:

  • a pure pipe is not called as often as a method in a component
  • but it doesn’t run again if the input value is mutated, so use carefully.

Split your template wisely

Based on what we learned, here is a trick that doesn’t use a specific Angular API, but can be easily understood.

Let’s say you have a component displaying a huge list of results, and an input allowing to update this list. As you don’t want to update the list on every key pressed, you are debouncing what the user types, and then update the list. Something like:

@Component({
  selector: 'ns-results',
  template: `
    <input [formControl]="search">
    <h1>{{ resultsTitle() }}</h1>
    <div *ngFor="let result of results">{{ result }}</div>
  `
})
export class ResultsComponent implements OnInit {

  search = new FormControl('');
  results: Array<string> = [];

  constructor(private searchService: SearchService) {
  }

  ngOnInit() {
    this.search.valueChanges
      .pipe(
        debounceTime(500),
        switchMap(query => this.searchService.updateResults(query))
      )
      .subscribe(results => this.results = results);
  }

  resultsTitle() {
    return `${this.results.length} results`;
  }
}

You may think that the change detection is not very often called, as you update the list only when the user has stopped typing. But in fact the change detection is called on every event in the template (so here on every key pressed). You can check it out by adding a simple console.log in resultTitle, and see it called in the developer console on every key pressed.

To avoid detecting change on the list elements even if not needed (as the results will not change on every new value, but only after some time), the idea is to split your view into two parts, and to introduce a sub-component to display the results. This component can be switched to OnPush and the change detection will only update it when really needed, and not on every key press.

@Component({
  selector: 'ns-results',
  template: `
    <input [formControl]="search">
    <ns-results-list [results]="results"></ns-results-list>
  `
})
export class ResultsComponent implements OnInit {

  search = new FormControl('');
  results: Array<string> = [];

  constructor(private searchService: SearchService) {
  }

  ngOnInit() {
    this.search.valueChanges
      .pipe(
        debounceTime(500),
        switchMap(query => this.searchService.updateResults(query))
      )
      .subscribe(results => this.results = results);
  }
}

With the sub-component looking like:

@Component({
  selector: 'ns-results-list',
  template: `
    <h1>{{ resultsTitle() }}</h1>
    <div *ngFor="let result of results">{{ result }}</div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ResultsListComponent {

  @Input() results: Array<string> = [];

  resultsTitle() {
    return `${this.results.length} results`;
  }
}

This is a simple pattern to use, often referred to as the smart/dumb component pattern: a smart component deals with data loading, event handling, etc., and simply passes the data to display as input to a second, dumb component. The only responsibility of the dumb component is to display the data, and to emit events to its parent smart component using outputs. This dumb component is the one with the large template, containing many expressions. But since its state only changes when its smart parent passes a new input, it can use OnPush and thus saves a lot of expression evaluations.

Attribute decorator

When using an @Input() in a component, Angular assumes that the value passed as input can change, and does what it takes to detect the change and pass the new value to the component. Sometimes, it’s not really necessary, as you may want to only pass a value once to initialize the component and never change it. In this very specific case, you can use the @Attribute() decorator instead of the @Input() one.

Let’s consider a button component, to which you want to pass a type to set its aspect (something like primary, success, warning, danger…).

Using an input, it would look like:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'ns-button',
  template: `
    <button type="button" class="btn btn-{{ btnType }}">
      <ng-content></ng-content>
    </button>`
})
export class ButtonComponent {

  @Input() btnType;
}

that you can use with:

<ns-button btnType="primary">Hello!</ns-button>
<ns-button btnType="success">Success</ns-button>

Since the input is a simple string that never changes, you can switch to use an attribute:

import { Attribute, Component } from '@angular/core';

@Component({
  selector: 'ns-button',
  template: `
    <button type="button" class="btn btn-{{ btnType }}">
      <ng-content></ng-content>
    </button>`
})
export class ButtonComponent {

  constructor(@Attribute('btnType') public btnType: string) {}
}

This produces a “bind-once” like effect, avoiding Angular to do unnecessary work. But keep in mind this only works with non-dynamic, string inputs.

Conclusion

This series of blog posts hopefully taught you some techniques which can help solve performance problems. But remember the golden rules of performance optimization:

  • don’t
  • don’t… yet
  • profile before optimizing.

As a famous computer scientist said:

Premature optimization is the root of all evil. - Donald Knuth

So strive to make the code as simple and correct and readable as possible, and only start thinking about profiling, then optimizing, if you have a proven performance problem.

If you enjoyed this blog post, you may want to dig deeper with our ebook, and/or with a complete exercise that we added in our online training. The exercise takes an application and walks you through what we would do to optimize it, measuring the benefits of each steps, showing you how to avoid the common traps, how to test the optimized application, etc. Check it out if you want to learn more!


Angular Performances Part 4 - Change detection strategies

This is the fourth part of this series (check the first part, the second one and the third one if you missed them), and this blog post is about how you can improve the runtime performances of your Angular application with change detection strategies. If you are the lucky owner of our ebook, you can already check the other parts if you download the last ebook release.

Now that we have talked about first load, reload and profiling we can continue our exploration of the tips for better runtime performances.

Change detection strategies

When we explained how Angular detects the changes in your application, we showed the tree of components and said that Angular starts by checking the root component, then its children, then its grand-children, until all components are checked. Then all the necessary DOM updates are applied in one batch.

But you may be wondering if it is a very good idea to check every component on every change. And you’re right, that’s often not really necessary.

Angular offers another change detection strategy: it’s called OnPush and it can be defined on any component.

With this strategy, the template of the component will only be checked in 2 cases:

  • one of the inputs of the component changed (to be more accurate, when the reference of one of the inputs changes);
  • an event handler of the component was triggered.

This can be very convenient when the template of a component only depends on its inputs, and can give a serious boost to your application if you display a lot of components on screen! But once again, be very cautious before applying this optimization: if the preconditions end up not being respected, you will lose your hairs wondering why the component (or any of its descendants) isn’t always repainting itself after a change.

Let’s take a small example to demonstrate.

Imagine that we have 3 components. A very simple ImageComponent:

@Component({
  selector: 'ns-img',
  template: `
      <p>{{ check() }}</p>
      <img [src]="src">
  `
})
export class ImageComponent {
  @Input() src: string;

  check() {
      console.log('image component view checked');
  }
}

used in a PonyComponent:

@Component({
  selector: 'ns-pony',
  template: `
    <p>{{ check() }}</p>
    <ns-img [src]="getPonyImageUrl()"></ns-img>
  `
})
export class PonyComponent {
  @Input() ponyModel: PonyModel;

  check() {
    console.log('pony component view checked');
  }

  getPonyImageUrl() {
    return `images/pony-${this.ponyModel.color}-running.gif`;
  }
}

used itself in a RaceComponent:

@Component({
  selector: 'ns-race',
  template: `
    <h2>Race</h2>
    <p>{{ check() }}</p>
    <div *ngFor="let pony of ponies">
      <ns-pony [ponyModel]="pony"></ns-pony>
    </div>
    <button (click)="changeColor()">Change color</button>
  `
})
export class RaceComponent {

  ponies: Array<PonyModel> = [{ id: 1, color: 'green' }, { id: 2, color: 'orange' }];
  colors: Array<string> = ['green', 'orange', 'blue'];

  check() {
    console.log('race component view checked');
  }

  changeColor() {
    this.ponies[0].color = this.randomColor();
  }

}

The RaceComponent displays two ponies, and the user can change the color of the first one by clicking on the Change color button.

With the current default change detection strategy, every time that we have a change in the application, all 3 components are checked.

We added a check() method in each component, called in each template: it allows us to track if the component is checked or not. And indeed in our example, we can see in our console:

pony component view checked
image component view checked
pony component view checked
image component view checked
race component view checked

(we can see that twice actually, because we are in development mode, see the section about enableProdMode above).

OnPush

But in this case, it’s a waste of time: we know that if the pony doesn’t change, the template of the PonyComponent doesn’t need to be checked. Same thing for the ImageComponent: if the src input is the same, there is no need to recompute the image URL. So let’s switch these components to OnPush, by adding a changeDetection attribute in their @Component decorator:

@Component({
  selector: 'ns-img',
  template: `
    <p>{{ check() }}</p>
    <img [src]="src">
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ImageComponent {
  @Input() src: string;

  check() {
    console.log('image component view checked');
  }
}

Same thing in PonyComponent:

@Component({
  selector: 'ns-pony',
  template: `
    <p>{{ check() }}</p>
    <ns-img [src]="getPonyImageUrl()"></ns-img>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PonyComponent {
  @Input() ponyModel: PonyModel;

  check() {
    console.log('pony component view checked');
  }

  getPonyImageUrl() {
    return `images/pony-${this.ponyModel.color}-running.gif`;
  }
}

When we click to change the color, we will only see in the console:

race component view checked

Which is awesome, because it means that we don’t check the components that we don’t need to check \o/.

OnPush and the mutability trap

But… there is a slight problem: the pony’s color doesn’t change any more!

I picked this example on purpose: even if OnPush is really powerful, it can be tricky and optimizing existing components is not only about adding a few OnPush here and there.

Why doesn’t it work in our case?

Take a closer look to our RaceComponent, and its changeColor method:

changeColor() {
  this.ponies[0].color = this.randomColor();
}

This method mutates the pony in the ponies collection, and this pony is the input of our PonyComponent. Now that we shifted our component to be OnPush, Angular will only run the change detection if the reference of the pony input changes. And when you mutate an object, it’s still the same object, so the reference doesn’t change, and Angular thinks there is no need to run the change detection…

So, is this change detection strategy completely useless? Not really, but it does require you to be more careful.

The simple way to fix our issue is to not mutate our pony in changeColor, but to create a new object:

changeColor() {
  const pony = this.ponies[0];
  // create a new pony with the old attributes and the new color
  this.ponies[0] = { ...pony, color: this.randomColor() };
}

Once you’ve done that, the application is faster and correct. If the user clicks on the button, the changeColor method creates a new pony object with the old attributes and the new color. As this is a new object, Angular will run the change detection in the PonyComponent (an input changed), and then the src input of the ImageComponent will also change, and the image will display the correct color. And, of course, if another event triggers the change detection in RaceComponent, the children component will not be checked (if their inputs did not change).

As you can see, you can quickly fall into a trap when migrating a component to an OnPush strategy, so be careful (unit tests are your friend).

One way to avoid this would be to use a library that enforces immutability. Immutable.js (by Facebook) is such a library. I’ve never used it professionally, so I can’t say if it’s a good fit in an Angular application or not, but you do see it mentioned often on the internets.

There is a last topic we need to talk about: observables.

OnPush, Observables and the async pipe

Let’s say we now have only one component, our well-known PonyComponent. It subscribes to an observable from a ColorService that returns a new color every second. We obviously expect the image displayed to change every second. The developer of this component thought that an OnPush change detection strategy couldn’t hurt. What do you think?

@Component({
  selector: 'ns-pony',
  template: `
      <p>New color every 1s</p>
      <img [src]="'pony-' + color + '.gif'">
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PonyComponent implements OnInit, OnDestroy {
  color = 'green';
  subscription: Subscription;

  constructor(private colorService: ColorService) {
  }

  ngOnInit() {
    this.subscription = this.colorService.get()
      .subscribe(color => this.color = color);
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

Sadly, this doesn’t work. With the OnPush strategy, Angular only refreshes the template if one of the inputs changed (here, there is no input), or if an event was triggered (there is none either). So the color field is updated every second, but the template is never refreshed…

This can be fixed by using the pipe called async.

The async pipe can be used to subscribe to a Promise or an Observable. Let’s use it in our PonyComponent:

@Component({
  selector: 'ns-observable-on-push-with-async',
  template: `<img *ngIf="color | async as c"
                  [src]="'pony-' + c + '.gif'">`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PonyComponent {
  color: Observable<string>;

  constructor(colorService: ColorService) {
    this.color = colorService.get();
  }
}

Now our component is working! The async pipe will trigger the change detection when a new value is received. And you can see that we store the result of async to use it with as. It also frees us to subscribe to the observable in the component, and to remember to unsubscribe when the component is destroyed: async does it for us.

Note that async can lead to several HTTP requests if used several times in a template, and that you can use the “smart/dumb” component pattern to make it easier to use OnPush.

ChangeDetectorRef

There are a few last tricks regarding change detection that I want to show you. They are for more advanced use cases but, you never know, it can be handy one day.

Let’s take an hypothetical use case: you have an observable that emits data very very frequently. In my example, it’s a clock that emits every 10 milliseconds:

@Component({
  selector: 'ns-clock',
  template: `
      <h2>Clock</h2>
      <p>{{ getTime() }}</p>
      <button (click)="start()">Start</button>
  `
})
export class ClockComponent implements OnDestroy {

  time = 0;
  timeSubscription: Subscription;

  start() {
    this.timeSubscription = interval(10).pipe(
      take(1001), // 0, 1, ..., 1000
      map(time => time * 10)
    ).subscribe(time => this.time = time);
  }

  getTime() {
    return this.time;
  }

  ngOnDestroy() {
    this.timeSubscription.unsubscribe();
  }

}

The component uses the Default change detection strategy, so every time the observable emits a new value, the change detection is triggered, the template is refreshed and the clock value displayed is updated.

But, do we really need a hundred updates per second? Our eyes can’t see that fast, and it’s putting pressure on our browser for nothing. And remember that not only this component will be checked a hundred times per second, but the whole application too!

Maybe in that case it would be enough to refresh the time displayed every second for example. To do so, you can completely opt out from the automatic change detection in your component, and handle things yourself, by injecting in your component a ChangeDetectorRef. This class offers a few methods:

  • detach()
  • detectChanges()
  • markForCheck()
  • reattach()

The first two work together: you can indicate to Angular to not care about the component with detach and then manually call detectChanges when you want the change detection to run:

@Component({
  selector: 'ns-clock',
  template: `
    <h2>Clock</h2>
    <p>{{ getTime() }}</p>
    <button (click)="start()">Start</button>
  `
})
export class ClockComponent implements OnDestroy {
  time: number;
  timeSubscription: Subscription;

  constructor(private ref: ChangeDetectorRef) {
    this.ref.detach();
  }

  start() {
    this.timeSubscription = interval(10).pipe(
      take(1001), // 0, 1, ..., 1000
      map(time => time * 10)
    ).subscribe(time => {
      this.time = time;
      // manually trigger the change detection every second
      if (this.time % 1000 === 0) {
        this.ref.detectChanges();
      }
    });
  }

  getTime() {
    return this.time;
  }

  ngOnDestroy() {
    this.timeSubscription.unsubscribe();
  }

}

As you can see, we slightly changed the component to inject ChangeDetectorRef, detach the component from the change detection, and then manually run detectChanges() to trigger it when we need it (every second in our case). The time field is still updated a hundred times per second, but now the clock displayed to our users is only updated every second!

Note that this only triggers a change detection on that component (and its children) every time we run detectChanges().

But there is a way to go one step further, and completely handle it manually, by updating the DOM yourself (and not triggering a complete change detection):

@Component({
  selector: 'ns-clock',
  template: `
      <h2>Clock</h2>
      <p #clock></p>
      <button (click)="start()">Start</button>
  `
})
export class ClockComponent implements OnDestroy {
  time: number;
  timeSubscription: Subscription;
  @ViewChild('clock') clock: ElementRef<HTMLParagraphElement>;

  constructor(private ref: ChangeDetectorRef) {
    this.ref.detach();
  }

  start() {
    this.timeSubscription = interval(10).pipe(
      take(1001), // 0, 1, ..., 1000
      map(time => time * 10)
    ).subscribe(time => {
      this.time = time;
      if (this.time % 1000 === 0) {
        this.clock.nativeElement.textContent = `${time}`;
      }
    });
  }

  ngOnDestroy() {
    this.timeSubscription.unsubscribe();
  }

}

Here we grab a reference to the element we need to update, and then we update the DOM manually when needed, without triggering a change detection.

Another way to do this is possible: you can completely run the code outside of Zone.js, the library that triggers the change detection. To do so, you can inject NgZone, and then use its runOutsideAngular method to execute code outside of its scope:

constructor(private zone: NgZone) {
}

start() {
  this.zone.runOutsideAngular(() => {
    this.timeSubscription = interval(10).pipe(
      take(1001), // 0, 1, ..., 1000
      map(time => time * 10),
    ).subscribe(time => {
      this.time = time;
      if (this.time % 1000 === 0) {
        this.clock.nativeElement.textContent = `${time}`;
      }
    });
  });
}

This produces the same results, but here the rest of the component would still be checked automatically by Angular. runOutsideAngular is more suited to use cases where you want only specific portions of code to run out of the watch of Zone.js/Angular.

As I was saying, this example is a bit advanced, but ChangeDetectorRef can be handy for some use cases. Imagine that the example changing the color of a pony every second doesn’t use an observable, but a simple setInterval.

@Component({
  selector: 'ns-pony',
  template: `<img [src]="getPonyImageUrl()">`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PonyComponent implements OnInit, OnDestroy {

  @Input() ponyModel: PonyModel;
  private intervalId: number;

  ngOnInit() {
    this.intervalId = window.setInterval(() => {
      this.ponyModel.color = this.randomColor();
    }, 1000);
  }

  ngOnDestroy(): void {
    window.clearInterval(this.intervalId);
  }

No visual update… And in that case, we can’t use the async pipe as we did with an observable…

But we can use the markForCheck method of ChangeDetectorRef to manually trigger the change detection in an OnPush component:

@Component({
  selector: 'ns-pony',
  template: `<img [src]="getPonyImageUrl()">`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PonyComponent implements OnInit, OnDestroy {

  @Input() ponyModel: PonyModel;
  private intervalId: number;

  constructor(private ref: ChangeDetectorRef) {
  }

  ngOnInit() {
    this.intervalId = window.setInterval(() => {
      this.ponyModel.color = this.randomColor();
      this.ref.markForCheck();
    }, 1000);
  }

  ngOnDestroy(): void {
    window.clearInterval(this.intervalId);
  }

And it works again!

If you enjoyed this blog post, you may want to dig deeper with our ebook, and/or with a complete exercise that we added in our online training. The exercise takes an application and walks you through what we would do to optimize it, measuring the benefits of each steps, showing you how to avoid the common traps, how to test the optimized application, etc. Check it out if you want to learn more!

See you soon for part 5!


Angular Performances Part 3 - Profiling and runtime performances

This is the third part of this series (check the first part and the second one if you missed them), and this blog post is about how you can profile the runtime performances of your Angular application and how you can improve these runtime performances. If you are the lucky owner of our ebook, you can already check the other parts if you download the last ebook release.

Now that we have talked about first load and reload, we can start talking about runtime performances. But if you run into a performance issue, before trying any of the following tips, you should start by measuring and profiling the application.

Browsers nowadays offer nice developer tools, especially Chrome, which allows to record your application, and analyze its behavior with quite some details. You can even simulate some conditions, like using a slower processor, or using a 3G network. You can also dive into the call hierarchy, and see how much time each function call is consuming.

Profiling

But Angular also offers a precious tool: ng.profiler. It’s not very well-known, but it can be handy as it allows to measure how long a change detection run in the current page took.

You can then try to apply one of the tips we’ll see, and measure again to see if there is any improvement.

In your main.ts file, replace the application bootstrapping code with the following:

platformBrowserDynamic().bootstrapModule(AppModule)
  .then(moduleRef => {
    const applicationRef = moduleRef.injector.get(ApplicationRef);
    const componentRef = applicationRef.components[0];
    // allows to run `ng.profiler.timeChangeDetection();`
    enableDebugTools(componentRef);
  })
  .catch(err => console.log(err));

Then go to the page you want to profile, open your browser console, and execute the following instruction:

> ng.profiler.timeChangeDetection()
ran 489 change detection cycles
1.02 ms per check

You can see how many change detection cycles it ran (it should be at least 5 cycles or during at least 500ms), and the time per cycle. This is a super useful metric, as many of the tricks we are going to show you directly act on the change detection system. You’ll be able to try them, run the profiler again, and compare the results.

You can also record the CPU profile during these checks to analyze them with ng.profiler.timeChangeDetection({ record: true }).

The Angular team recommends to have a time per check below 3ms, to leave enough time for the application logic, the UI updates and browser’s rendering pipeline to fit within the 16 milliseconds frame (assuming a 60 FPS target frame rate).

Let’s discover these tips!

Runtime performances

Angular’s magic relies on its change detection mechanism: the framework automatically detects changes in the state of the application and updates the DOM accordingly. So, as a general rule of thumbs, you’ll want to help Angular and limit the change detection triggering and the amount of DOM to update/create/delete.

To be honest, most applications will be fine, even under heavy load. But some of us will have to recode Excel in the browser for their enterprise, or will have a component with a tree displaying 10,000 customers, or another unreasonable thing to do in a browser. These things are tricky, whatever framework you use. They tend to update a lot of DOM, and have to check a lot of components. A few of the following tricks can help. And a few of these tricks are really mandatory, like the first one.

enableProdMode

When you are in development mode (by default), Angular will run the change detection twice every time there is a change. This is a security to make sure you are not doing strange things, like updating data without following the one-way data flow. If you break the rules, Angular will warn you about it in development, by throwing an exception that will force you to fix your code. But if you are not careful, you will deploy the application in this mode, and change detection will still run twice, slowing your application.

To go in production mode, you need to call a function provided by Angular called enableProdMode. This method will disable the double check, and also make the generated DOM “lighter” (less attributes on the elements, attributes that are added to debug the application).

As usual the CLI got you covered, and the call to enableProdMode is already present in the generated application, wrapped in an environment check: if you build with the production environment, your app will be in production mode.

trackBy in ngFor

This is a simple tip that can really speed things up on *ngFor: add a trackBy. To understand why, let me explain how modern JS frameworks (at least all major ones) handle collections. When you have a collection of 3 ponies and want to display them in a list, you’ll write something like:

<ul>
  <li *ngFor="let pony of ponies">{{ pony.name }}</li>
</ul>

When you add a new pony, Angular will add a DOM node in the proper position. If you update the name of one of the ponies, Angular will change just the text content of the right li.

How does it do that? By keeping track of which DOM node references which object reference. Angular will have an internal representation looking like:

node li 1 -> pony #e435 // { id: 3, color: blue }
node li 2 -> pony #8fa4 // { id: 4, color: red }

It works great, and if you change an object for another one, Angular will destroy the node and build another one.

node li 1 (recreated) -> pony #c1ea // { id: 1, color: green }
node li 2 -> pony #8fa4 // { id: 4, color: red }

If the whole collection is updated with new objects, the complete DOM list will be destroyed and recreated. Which is fine, except when you just refresh a list with almost the same content: in that case, Angular destroys the complete node list and recreates it, even if there is no need to. For example, when you fetch the same results from the server, you will have the same content, but different references as your collection will have been recreated.

The solution for this use-case is to help Angular track the objects, not by their references, but by something that you know will identify the object, typically an ID.

For this, we use trackBy, which expects a method:

<ul>
  <li *ngFor="let pony of ponies trackBy: ponyById">{{ pony.name }}</li>
</ul>

with the method defined in the component:

ponyById(index: number, pony: PonyModel) {
  return pony.id;
}

As you can see, this method receives the current index and the current entity, allowing you to be creative (or simply track by index, but that’s not recommended).

With this trackBy, Angular will only recreate a DOM node if the id of the pony changes. On a very big list which doesn’t change much, it can save a ton of DOM deletions/creations. Anyway, it’s quite cheap to implement and doesn’t have cons, so don’t hesitate to use it. It’s also a requirement if you want to use animations. If a DOM element’s style is supposed to be animated (by transitioning smoothly from the previous value to the new one), and the list of ponies is replaced by a new one when refreshed, then trackBy is a must: without it, the animation will never happen, because the style of the element never changes. Instead, it’s the element itself which is being replaced by Angular.

We have more tips for you, but you’ll have to wait until next week to read about them!

If you enjoyed this blog post, you may want to dig deeper with our ebook, and/or with a complete exercise that we added in our online training. The exercise takes an application and walks you through what we would do to optimize it, measuring the benefits of each steps, showing you how to avoid the common traps, how to test the optimized application, etc. Check it out if you want to learn more!

See you soon for part 4!


Posts plus anciens