What's new in Angular 9.0?

Angular 9.0.0 is here!

Angular logo

Ivy, sweet Ivy

This is a long awaited release for the community, as Ivy is now the default compiler/renderer in Angular 🌈.

If you want to learn more about Ivy itself, check out our dedicated blog post.

In a few words, if you’re in a hurry, Ivy is a complete rewrite of the underlying compiler and renderer. The main goals are:

  • a cleaner architecture of the framework, which is a stepping stone to optimizations and new features in the future
  • a faster and stricter compilation, to help developers and avoid slowing them down
  • smaller bundle sizes, especially in big applications right now, and all over the place in the future.

As you can probably imagine, Ivy is a very big change internally, and the team worked a lot to ensure that our applications are compatible. Bundle sizes should improve in the future, as well as runtime performances (which are currently roughly on par with View Engine, faster in some cases, slower in others).

As this is a big change, I strongly encourage you to thoroughly test this update, more thoroughly than the v5, v6, v7 or v8 updates. If you encounter a problem, open an issue with a reproduction, and stay on v8 or use v9 without Ivy for the moment (enableIvy: false).

The new design will allow some cool features in the future, like high order components, a better i18n, etc. For this last point, guess what? It’s already here!

Internationalization with $localize

A new package appears with this release: @angular/localize. This package is now in charge of all i18n concerns, and offers quite a few interesting things.

In fact there are so many topics to cover that we wrote a dedicated article about it: Angular i18n with $localize.

This article talks about compile-time i18n, the much awaited possibility to have translations in code, and how all this works under the hood. Check it out!

Note that even if you are not using i18n in your application, but that one of your dependencies does (like ng-bootstrap for example), then you have to add @angular/localize to your application. This is fairly straightforward:

ng add @angular/localize

But really you should check out the blog post we wrote or the updated chapter in our ebook.

Other i18n goodies

Unrelated to this new package, the locale data now include the directionality of the locale (ltr or rtl). You can get this information by using getLocaleDirection(locale).

Angular 9 also offers the possibility to configure the default currency! The currency pipe allows to specify which currency you want to use by providing an ISO string like USD, ‘EUR’… If you don’t provide one, it uses USD by default. So it is a bit cumbersome to have to specify the currency every time, if your application only uses EUR for example.

Angular 9 introduced the possibility to configure the default currency globally using the token DEFAULT_CURRENCY_CODE:

{ provide: DEFAULT_CURRENCY_CODE, useValue: 'EUR' },

And you can then use currency without specifying EUR in a component. You can also get the currency via dependency injection of course:

@Component({
  selector: 'ns-currency',
  template: `
    <p>The currency is {{ currency }}</p>
    <!-- will display 'EUR' -->
    <p>{{ 1234.56 | currency }}</p>
    <!-- will display '1 234,56 €' -->
  `
})
class DefaultCurrencyOverriddenComponent {
  constructor(@Inject(DEFAULT_CURRENCY_CODE) public currency: string) {}
}

It also offers a getLocaleCurrencyCode(locale) function to retrieve the default currency for a specific locale.

Note that in Angular 10 the behavior will change, and the currency pipe will always use the default currency of the configured locale, even you don’t specify the DEFAULT_CURRENCY_CODE!

Better type-checking

In the Ivy section, I was mentioning that the compilation is a bit stricter. And indeed it is smarter! If you use fullTemplateTypeCheck, you’ll have the same level of strictness that you had before. But if you want to go one step further, you can try the strictTemplates option in your tsconfig.json:

"angularCompilerOptions": {
  "strictTemplates": true
}

Until now, you could for example give a string to @Input() that was expecting a number: you now get a nice compilation error. ngFor elements were previously typed as any, and are now properly typed. So:

<div *ngFor="let user of users">{{ user.nam }}</div>

is now caught by the compiler if you typed nam instead of name. The $event parameter and #ref variables in templates are now also properly typed.

strictTemplates is just a shortcut to activate a bunch of strictness flags, so if one particular check bothers you, you can disable it. For example, if you don’t want to check the type of the inputs:

"angularCompilerOptions": {
  "strictTemplates": true,
  "strictInputTypes": false
}

Check out the official list of flags in the documentation.

As a component author, there is a convoluted way to accept other types than the declared one for your input. It is mostly useful for library maintainers, and you can check out the official documentation for an example.

Better auto-completion

The language service (the package responsible for the nice auto-completion we have in our IDEs) has been improved and now offers some really nice new features. For example if you hover on a component or a directive in template, you’ll see if it is the former or the latter and from which module it comes from. It should be way smarter and more robust overall.

TypeScript 3.7

This release also comes with the need to upgrade to at least TypeScript 3.6, and you can even use TypeScript 3.7. You can check out which new features TS 3.6 and TS 3.7 offers on the Microsoft blog posts (optional chaining, yay \o/).

Automatic migrations

If you are using the CLI ng update @angular/core command, you’ll notice that several automatic migrations will run.

If you are still using the deprecated Renderer, the schematic updates your code to use Renderer2. You can read more about that in the official deprecation guide. The Renderer class was deprecated for a long time and has been removed from Angular.

If you are using a base class for a component or a directive, that base class must be annotated with a simple @Directive() decorator for Ivy. For example:

class WithRouter {
  constructor(public router: Router) {}
}

@Component({
  // ...
})
export class MenuComponent extends WithRouter {
  // uses `this.router` from `WithRouter`

Then after the migration, WithRouter has a @Directive() decorator added on its class:

@Directive()
class WithRouter {
  constructor(public router: Router) {}
}

Note that this @Directive() decorator does not have a selector: this was not possible before v9, and it has been introduced for this use-case specifically.

The @Directive() decorator is also added to your base class if it has decorated fields, like @Input(), @Output(), @ViewChild(), @HostBinding(), etc..

Another migration adds an @Injectable() decorator on all services that don’t have one. It was not necessary for View Engine (check this blog post if you want to know why) but it is now with Ivy. So everything that is referenced as a service in your application must now have an @Injectable() decorator.

ModuleWithProvider must also provide its generic type, so a schematic adds it if it is missing in your code.

You may also remember the static flag added in Angular 8.0, to ease the migration to Ivy. If you don’t, check out what we wrote about it at that time in the Query timing section. This flag is now no longer required now that Angular uses Ivy. So a migration will remove every static: false you have in your codebase, as it is the default value.

All the migrations are properly documented in the official documentation for the v9 update.

Dependency injection

Until now, when using @Injectable(), you could give only two values: an Angular module or root (which is an alias for the root module of your application). It now also accepts platform and any. The latter provides a unique service instance in every module (including lazy modules) that injects the token, and the former could be useful if you have several Angular applications or Angular Elements on the same page, and want them to share a service instance.

Deprecations

entryComponents is no longer necessary

Until now you had to declare the component dynamically loaded, typically in a modal, in the entryComponents of a module. This is no longer necessary with Ivy! You can safely remove entryComponents from your modules.

TestBed.inject

The TestBed.get method that you probably use all over your unit tests has been deprecated and replaced by TestBed.inject. TestBed.get returns any, so you had to manually type the value returned. It is now no longer necessary with TestBed.inject. You can then replace:

const service = TestBed.get(UserService) as UserService;

by

const service = TestBed.inject(UserService);

Breaking changes

NgForm

The directive NgForm used to have ngForm as a possible selector and it’s now no longer the case. It had already been deprecated in Angular 7.0.

Hammer

Until v9, an Angular application was offering Hammer.JS support by default, even if you were not using it. To gain a few kB in our bundles, this is no longer the case, and you’ll have to import HammerModule explicitly in your root module if you want to keep using Hammer.

Intl API

The i18n pipes (number, percent, currency, date) were using the Intl API, and were rewritten to not depend on it in Angular 5.0. The current version of these pipes works without the Intl API. If you really wanted to keep the old version, you had to import DeprecatedI18NPipesModule in your application when migrating to Angular 5.0. This is no longer possible as all the deprecated pipes have been removed.

This is a big update, and I’m sure you’re dying to try it 😎. You can also check out our blog post about the CLI v9 to see what’s new there.

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



blog comments powered by Disqus