Angular 12 in Depth

Everything you need to know about Angular 12

Angular 12 in Depth

Angular 12 has just been released. Time to discover what's new!

In this article, I’ll go over (almost) everything noteworthy in this brand new release. I’ll also highlight what’s changed around Angular, just like I did with my previous articles about Angular 11 and Angular 10.

If you're just looking for the bird-eye view, then check out the official release announcement. Here, I'll dig much deeper into the release notes.

Join the Ivy league...

The Angular team has been working on Ivy (the new compilation & rendering pipeline) since 2018. It was finally released with Angular 8. Since Angular 9, Ivy has been the default for new projects, and the ecosystem is slowly migrating to it. With Angular 12, the old View Engine is now officially deprecated. It will be removed in a future major release. Also, it won't be possible to create new applications using View Engine. Finally, Ivy is now the default for new library projects.

At this point, library authors can still rely on View Engine thanks to ngcc (the compatibility compiler of Angular), but it's really time for them to evaluate whether they can migrate their libraries to Ivy or not. A few weeks ago, Minko Gechev has published an article to explain that in detail. Also, check out the related RFC.

In case you didn't know, ngcc is the small process that runs after you create an Angular project or install dependencies. When you see messages like Compiling ... : es2015 as esm2015, it's ngcc doing its job. What ngccdoes is compile libraries that rely on View Engine so that Ivy can consume those.

Like me, you've probably noticed that ngcc takes a lot of time to execute, and has a very negative impact on developer experience. This is why it is crucial for the Angular ecosystem to migrate to Ivy as soon as possible. Second, until a majority of the ecosystem has moved on, the Angular team will have to keep View Engine around, which is problematic for a number of reasons. Last but not least, libraries that rely on View Engine can't depend on Ivy ones.

Library authors probably can't migrate to Ivy too fast, but they should clearly push reluctant users to upgrade. Anyways, the path forward is to migrate all the things to Ivy asap ;-)

As a side note, if you're not familiar with the internals of Angular, then check out the following videos.

How Angular works by Kara Erickson:

Deep Dive into the Angular Compiler by Alex Rickabaugh:

There's also an excellent article about Ivy over there.

Bye Protractor

In April, the Angular team has announced plans to end the support of Protractor at the end of 2022.

As of Angular 12, Protractor won't be included by default in new projects. Instead, the Angular CLI will provide options to use other solutions like Cypress, WebdriverIO, or TestCafe. This means that the ng e2e command should continue to be supported in the future.

As explained in the announcement, back in 2013 when Protractor was created, WebDriver was not a standard, and end-to-end (e2e) tests were hard to write, and a nightmare to maintain. Protractor brought an innovative solution by wrapping selenium-webdriver, and provided a way to control the execution flow.

Of course, a lot of things have evolved since then. We now have the WebDriver API, async and await (even top-level await, woah). Also, the ecosystem has also evolved. Solutions like Cypress, Playwright, Puppeteer have gained a lot of (well-deserved) popularity. Tools like Cypress for example provide a much better developer experience than Protractor, leverage the modern standards, and even support cross-browser testing (among other powerful capabilities). Another benefit of the current de-facto e2e testing tools is that they're framework-agnostic, which is very valuable.

Long story short, maintaining Protractor doesn't make much sense for the Angular team. Evolving Protractor now would require too much effort and induce a ton of breaking changes. You can find more details in the RFC, it's an interesting read.

The timeline is important for teams/projects that have invested a lot of time and energy writing e2e tests with Protractor. The Angular team is still busy evaluating the feedback collected through the RFC, so we'll probably know more later this year.

Anyways; give Cypress a try if you haven't already, you won't be disappointed! By the way, I keep recommending everyone to start using Nrwl NX, a wonderful solution that includes support for Angular, React, Next.js, Cypress, and a lot more ;-)

Nullish coalescing

Angular 12 includes support for using the nullish coalescing operator in Angular templates. And this is awesome! That operator has been supported in TypeScript since version 3.7.

If you haven't heard about this operator, then let me give you a quick explanation. The idea is to be able to define a fallback value in case something is null or undefined. Here's an example:

let x = foo ?? true;

If foo is null or undefined, then x will be set to true (i.e., the fallback value), and we could set it to anything we like.

Without this awesome ?? operator, we would have to write this instead:

let x = foo !== null && foo !== undefined ? foo : true;

Now that Angular supports it too, we can finally write expressions such as:

{{ age ?? calculateAge() }}

Instead of the old and more verbose alternative. NEAT!

You can learn more about this change here.

You can find more information about nullish coalescing in the TypeScript handbook as well as on MDN.

TypeScript 4.2 support

Angular 12 introduces support for TypeScript 4.2, which means that we can now use a ton of new wonderful language features. Also, support for TypeScript 4.0 and 4.1 has been dropped.

Here's a quick rundown of what TS 4.2 includes:

Smart Type Alias Preservation: The expected types are displayed by code editors instead of raw types as it occurred before. You can learn more about that here.

Leading/Middle Rest elements in Tuple Types: We can now include rest elements anywhere within a tuple (with a few caveats). This is pretty cool for those of us who rely on tuples from time to time. Learn more about that here.

Understanding your project structure: TS 4.2 includes a new flag called --explainFiles, which instructs the TypeScript to output information about why a filed was included in your program. This is very useful for troubleshooting.

Improvements for uncalled function checks: TypeScript can now detect more cases where functions aren't being called. For instance when writing foo instead of foo(). More details and examples can be found here.

Destructured variables can now be explicitly marked as unused: let [_first, second] = getValues(); , which is awesome; no more errors when noUnusedLocals is enabled!

There's a lot more actually, like stricter checks for the in operator, and a few breaking changes that might impact you. So make sure to check out the release notes.

Webpack 5 support

Angular 11 brought us experimental support for Webpack 5. With Angular 12, the Webpack 5 support is now production-ready. w00t!

If you haven't looked at Webpack 5, you're going to be amazed. Webpack 5 was released back in October 2020, so it's quite stable by now. Webpack is currently at version 5.37 (released a few days ago).

Here's a short explanation about what changed with Webpack 5, and why I'm so glad about this 🤩

First off, as you know, Webpack is the key piece of the Angular CLI puzzle, and it plays a big role for bundle size, build performance, etc.

Second, Webpack 5 is a major release for a reason. It includes a number of breaking changes, which explains why it took a while for Angular and a gazillion utilities/libraries in the ecosystem to upgrade.

In the release notes, the Webpack team indicated that Webpack 5 focuses on:

  • Improving the build performance with persistent caching
  • Improving long-term caching with better algorithms & defaults
  • Improving bundle size with better Tree Shaking and code generation
  • Improving compatibility with the Web platform
  • Cleaning up internal structures
  • Introducing breaking changes (haha) now, allowing them to stay on v5 for as long as possible

The coolest feature of Webpack 5 is its support for Module Federation, which provides the foundations necessary to facilitate the creation of micro front-ends. It's a bit out of scope of this article, but in a jiffy, module federation makes it possible for different builds to behave like a huge connected module graph, and allows us to import and use modules from remote builds. Check out the official documentation to know more.

If you're curious, then you should take a look at Manfred Steyer's talk about that subject:

Among the major changes, Webpack 5 has dropped everything that was previously deprecated (e.g., NoEmitOnErrorsPlugin, ModuleConcatenationPlugin, NamedModulesPlugin), as well as IgnorePlugin and BannerPlugin.

Also, the Node.js polyfills that were previously automatically injected have been removed; and that is one of the biggest changes in that release. Those polyfills initially allowed Webpack to let us use modules made for Node.js in the browser. That was cool, but it had a major downside: larger bundles. In addition, those polyfills were less and less useful, as there were either browser-compatible alternatives of libraries or specific distributions with Browser support. As of Webpack 5, since those polyfills won't be added automagically anymore, we might stumble upon some surprises. For instance in cases where we use modules made for Node.js in the browser without realizing that it previously worked thanks to Webpack. You can learn more about that here.

Webpack 5 has introduced better support for long-term caching. In production mode, it now includes new algorithms by default:

  • chunkIds: "deterministic"
  • moduleIds: "deterministic"
  • mangleExports: "deterministic"

As the value indicates, those algorithms assign deterministic IDs to chunks and modules, and deterministic names to exports.

Webpack 5 is able to perform nested tree shaking, by tracking access to nested properties of exports (welcome to the Matrix), which should further improve tree shaking. Also, it can now analyze dependencies between the exports and imports of a module. There are also improvements to CommonJS tree shaking. And there are a ton more optimizations.

Webpack 5 also includes a number of changes to improve the developer experience. For example, there's a new named chunk id algorithm that is enabled by default in development mode. That new algorithm gives human-readable names to chunks. The target property has also been vastly improved.

To be honest, I don't have enough space here to cover everything new with Webpack 5, there's just way too much. So I'll stop here ;-)

If you only use Webpack indirectly through the Angular CLI, then most of this should be "transparent" for you. But if you're using a custom Webpack build in your project, then you should probably take a look at the migration guide.

Finally, if you're curious about what's coming next with Webpack, then check out the roadmap for 2021 and of course the latest release notes.

IE11 support is deprecated

As sad as it may sound (who am I kidding? 😂), Angular 12 is deprecating support for IE11. Internet Explorer is dead for most of us, but unfortunately, many organizations still use it in production. IE 11 support will thus be removed by Angular 13, which means that those organizations really need to start moving away from IE (which is a good thing anyway). No more excuses! ;-)

Once the IE support is gone, Angular will be smaller, faster, and thus better for all of us. Also, once the burden of maintaining backwards compatibility with legacy browsers will be gone (IE11 is the last of those!), then Angular will be able to leverage modern APIs (e.g., CSS variables, Intersection Observer, CSS Grid, Proxies, Web Animations, and more).

I really can't wait for IE support to be gone!

Strict by default

YES YES YES 🤩. As of Angular 12, the strict mode of Angular is enabled by default in the CLI. And this is awesome.

As you know, I'm a huge fan of TypeScript's strict mode, but also of Angular's strict mode. If you want to know more, then check out the article I wrote last year and Minko Gechev's article explaining the change.

Production build by default

Up until now, running the ng build command created a development build. As of Angular 12, ng build will now default to a production build.

Hopefully, it will help some teams avoid making the mistake of building & deploying development builds to production environments. Although, I fear that teams making that mistake will still have other issues to deal with ;-)

Sass support for inline styles

Angular supports Sass since a very long time but, so far, we could only use Sass in external stylesheets. With Angular 12, it is now possible to use Sass together with inline component styles (i.e., within the styles property).

This needs to be enabled by setting the new inlineStyleLanguage property to true in angular.json.

Tailwind support

Tailwind is now officially supported by Angular. Actually, support was introduced in the Angular CLI in v11.2.

Tailwind is busy taking over the world, especially now that it has a rad JIT compiler, and it's wonderful to have built-in support for it in Angular.

Previously, adding Tailwind to an Angular project required customizing the Webpack build. Not anymore! Now, adding Tailwind is as simple as installing the package, creating the tailwind.config.js file using npx tailwindcss init, and making sure to enable Tailwind's JIT mode.

Http improvements

Angular 12 introduces a number of improvements around its HTTP support.

Metadata for requests and interceptors

First, the HttpClient can now be used to store and retrieve custom metadata for requests. This is particularly useful for HTTP interceptors. This capability can be used through the new HttpContext.

Previously, it was complicated to provide context to interceptors, but now it will be much more straightforward. Now, the different HTTP methods provided by HttpClient include a new context: HttpContext option, which we can used to pass in a map.

Netanel Basal has written an article about this, so check it out if you want to know more.

appendAll on HttpParams

The HttpParams class now has a new appendAll method, which can be used to easily add a set of parameters at once:

appendAll(params: {[param: string]: string|string[]}): HttpParams

Params can now be passed as numbers and booleans

Previously, numbers and booleans could not directly be used as HTTP parameters. We had to transform those into strings. Not anymore!

HttpStatusCode

Angular has introduced its own list of human-readable names for HTTP status codes, in the form of a const enum.

Previously, we had to introduce our own solution if we wanted to have human-readable names. Thanks to this new feature, we can now instead use HttpStatusCode.

For example:

if (response.status === HttpStatusCode.Ok) {
 ...
}

For those who use TypeScript on both back-end and front-end, this isn't super useful (as we probably all have our own abstraction already), but if your project only uses TypeScript/Angular on the front-end, then it's a nice improvement.

XhrFactory

The XhrFactory class has been moved to a different package. It is now exposed by angular/common instead of angular/common/http.

Note that a migration has been included in the upgrade, so you won't even notice if you run ng update ;-)

Router changes

The Angular router has changed a bit in Angular 12.

First, the routerLinkActiveOptions directive has been enhanced. Now, it is possible to specify whether we require an exact match or not for different parts of the URL in order to add a CSS class to a link.

Previously, we could only require an exact match (or not) for the whole URL:

<a
  routerLink="/products/foo"
  routerLinkActive="highlight-product"
  [routerLinkActiveOptions]="{ exact: true }"
>
  foo
</a>

Now, we can instead provide fine-grained matching rules for paths, query parameters, matrix parameters, and fragments:

<a
  routerLink="/products/foo"
  routerLinkActive="highlight-product"
  [routerLinkActiveOptions]="{ paths: 'exact', queryParams: 'subset', matrixParams: 'ignored', fragment: 'ignored' }"
>
  foo
</a>

The supported values are exact, ignored and subset, which can be used for queryParams and matrixParams. For paths, you can either pass in exact or subset, and exact or ignored for the fragment.

Note that the isActive method of the router also accepts these new options.

fragment is nullable

Up until now, ActivatedRouteSnapshot.fragment was not nullable. This has changed with Angular 12. Don't worry too much though; ng update has got you covered.

Forms

More control over emitted events

The emitEvent option has been added to various methods of FormGroup and FormArray. Thanks to this change, it will now be possible to control whether events need to be emitted or not in more cases.

For instance, previously when a control was removed using the removeControl method of FormGroup, then an event was always emitted. With this change, we will now be able to avoid such problems.

The emitEvent option has been added to the following methods of FormGroup:

  • addControl
  • removeControl
  • setControl

And to the following methods of FormArray:

  • push
  • insert
  • removeAt
  • setControl
  • clear

min and max validators support for template-driven forms

The min and max validators of Angular can now be used with template-driven forms:

<input [(ngModel)]="foo.bar" [min]="min" [max]="42" />

Note that this is a breaking change, since those would previously be ignored.

i18n

The i18n system of Angular has changed with this release.

As pointed out in the announcement blog post, there are currently a number of legacy message id formats being used. Those are fragile and issues can appear because of whitespace, formatting templates & ICU expressions.

Angular 12 introduces a new canonical message id format (i.e., one format to rule them all). This format is more resilient and more intuitive.

This format will reduce the unnecessary translation invalidation and associated retranslation cost in applications where translations do not match due to whitespace changes for example.

Since v11, new projects are automatically configured to use the new message ids, and now there is tooling in place to migrate existing projects with existing translations. If you're concerned, then you can use the localize-migrate tool to migrate your message ids:

ng extract-i18n --format=legacy-migrate
npx localize-migrate --files=*.xlf --map-file=messages.json

You can find more information here.

Performance improvements

With this release, there are a number of performance improvements. The most obvious one comes along with the upgrade to Webpack 5, but there's more:

A number of unused methods have been removed from DomAdapter. It's cool because its methods aren't tree shakeable, and were included in all Angular applications, as explained in the corresponding PR.

Angular now generates less code for safe property access; for example for template expressions like a?.b and the newly supported null coalescing: a ?? b.

The Angular compiler now supports incremental compilation even in the presence of redirected source files. Previously, work from prior compilation could not be reused when TypeScript deduplicated source files. You can read more about that here.

The Angular compiler now caches the filesystem path of source files. Previously, it repeatedly called fs.resolve(), which is time-consuming.

The getDirectives function has been improved. Along with that change, the ng namespace has also been expanded to include a new getDirectiveMetadata utility.

And there's some more.

ng API improvements

The ng debugging API that we can use from the browser dev tools has been improved with Angular 12.

There's a new function called getDirectiveMetadata, which can be used to retrieve information about components and directives. I don't think we'll need it often, but future dev tooling improvements will most probably rely on this. You can find out more over here.

A new profiler function called esetProfiler has also been implemented, and it is also available in production mode. That new function can be called in a number of scenarios. For instance, it can be used to trace template creation durations, template updates, lifecycle hooks processing, etc. Again, this API will be taken advantage of by upcoming dev tools, giving us a lot more insightful information about how our applications are performing at runtime.

This is still experimental at this point, but I guess we'll hear more about this later on. I'm curious to see if/when tools like Sentry will integrate support for collecting this kind of information in order to provide us with useful performance dashboards.

New Dev Tools

A few days after the release of Angular 12, the Angular team has announced the availability of brand new Angular Dev Tools for Google Chrome. You can download those here.

Using that brand new browser extension, you can easily inspect your Angular applications during development. It supports:

  • Visualizing the structure of the application (i.e., inspecting the component tree)
  • Exploring and editing components
  • Profiling performances

Using the embedded profiler, we can record change detection events, and preview those as they occur. For each change detection cycle, we can look at how long it took, which components took the longest time, whether that cycle caused frame drops.

Angular previously had semi-official Dev Tools through the Augury project (which are the basis of the new tools!), but those were not compatible with Ivy. So this is great news for the Angular community!

And more...

There's a ton more minor changes in this release. Here's a quick overview.

APP_INITIALIZER now supports Observables

Up until now, it wasn't possible to use Observables with APP_INITIALIZER. As of Angular 12, we can now do so, which is going to allow for cleaner code!

In case you didn't know about this feature of Angular yet, APP_INITIALIZER is a token that we can use to define functions that need to execute during application initialization. If that function is asynchronous, returning a Promise or an Observable (new :p), then Angular waits for it to complete before starting the application.

This change is more than welcome, as it means that we can write even more code using RxJS, instead of having to "fall back" to the Promise API.

You can read more about that here.

Runtime control over animations

Previously, the only way to disable animations was to provide the NoopAnimationsModule. As of Angular 12, it is now possible to disable animations based on runtime information, using BrowserAnimationModule.withConfig method, and passing it the disableAnimations boolean property.

New historyGo method on Location service

The LocationService of Angular now includes a historyGo method, which can be used to navigate towards a specific page in the session history, identified by its relative position to the current page. This method corresponds to the native window.history.go method. Check out MDN for some examples.

Language service improvements

The language service, which is what provides IDEs all the useful insights about Angular code has also improved with this release.

With Angular 12, the language service is enabled by default (previously it required us to opt-in).

As of Angular 12, it will also detect if strict template type checking is not enabled, and will advise you to enable those.

The language service now also includes performance tracing capabilities, which can be used to trace performances. This can be enabled in VSCode, as explained here (this is fairly low level though).

If you don't know about the language service yet, check out the official docs, or the nice blog post written by Ninja Squad, or this video intro.

Disable lint rules directly from HTML templates

The Angular template compiler now keeps track of HTML comments.

Previously, it wasn't possible to disable linter rules from HTML templates because the Angular template compiler ignored comments. The workaround was to disable those rules for the whole template from the component's controller. Thanks to this change, it will now be possible to do this more precisely from the template:

<!-- eslint-disable-next-line @angular-eslint/template/no-any -->
<span>{{ $any(foo).bar }}</span>

Yay for cleaner code!

The DatePipe now supports ICU standard Stand Alone day of week

Previously, it wasn't possible to format a date to Stand Alone day of week using the DatePipe.

Thanks to this change, Finnish date formatting is now supported, and I'm sure that it is great news for a fraction of the Angular community 😉

Support for forwardRef in providedIn fields of Injectable decorators

It is now possible to use forwardRef within the providedIn field of the @Injectable decorator.

New transformResource hook on the ResourceHost interface

A transformResource method has been added to the ResourceHost interface. Thanks to this, it is now possible for tooling to do things like introducing preprocessor support for inline styles. This is what enables the new support for SASS with inline styles.

You can learn more about that here and here.

Possible to create custom router outlet implementations

Up until now, creating custom router outlets was possible, but required jumping through some hoops (i.e., registering custom outlets with ChildrenOutletContexts).

Angular 12 provides a cleaner support for custom router outlets.

Bug fixes

As usual, there's a gazillion bug fixes included in this release.

Here's a copy of the release notes:

  • animations: ensure consistent transition namespace ordering (#19854) (01cc995)
  • animations: update supported range of node versions to only include LTS versions (#41822) (e918250)
  • animations: cleanup DOM elements when the root view is removed (#41059) (c49b280)
  • animations: allow animations on elements in the shadow DOM (#40134) (dad42c8), closes #25672
  • animations: cleanup DOM elements when the root view is removed (#41001) (a31da48)
  • bazel: update supported range of node versions to only include LTS versions (#41822) (8503246)
  • bazel: update build tooling for latest changes in rules_nodejs (#40710) (696f7bc)
  • bazel: update integration test to use [email protected] (#40710) (34de89a)
  • bazel: update type castings for JSON.parse usage (#40710) (2c90391)
  • benchpress: update type castings for JSON.parse usage (#40710) (e721a5d)
  • common: add right ContentType for boolean values with HttpClient request body(#38924) (#41885) (922a602)
  • common: update supported range of node versions to only include LTS versions (#41822) (f2b6fd8)
  • common: viewport scroller not finding elements inside the shadow DOM (#41644) (c0f5ba3), closes #41470
  • common: temporarily re-export and deprecate XhrFactory (#41393) (7dfa446)
  • common: cleanup location change listeners when the root view is removed (#40867) (38524c4), closes #31546
  • common: allow number or boolean as http params (#40663) (91cdc11), closes #23856
  • common: avoid mutating context object in NgTemplateOutlet (#40360) (d3705b3), closes #24515
  • compiler: preserve @page rules in encapsulated styles (#41915) (3e365ba), closes #26269
  • compiler: strip scoped selectors from @font-face rules (#41815) (2a11cda), closes #41751
  • compiler: update supported range of node versions to only include LTS versions (#41822) (bae8126)
  • compiler: non-literal inline templates incorrectly processed in partial compilation (#41583) (ab257b3)
  • compiler: not generating update instructions for ng-template inside alternate namespaces (#41669) (2bcbbda), closes #41308
  • compiler: avoid parsing EmptyExpr with a backwards span (#41581) (e1a2930)
  • compiler: handle case-sensitive CSS custom properties (#41380) (e112e32), closes #41364
  • compiler: include used components during JIT compilation of partial component declaration (#41353) (ff9470b), closes #41104 #41318
  • compiler: support multiple :host-context() selectors (#40494) (07b7af3), closes #19199
  • compiler: update type castings for JSON.parse usage (#40710) (f728490)
  • compiler-cli: use '' for the source map URL of indirect templates (#41973) (7a4d980), closes #40854
  • compiler-cli: expose the linker as a Babel plugin (#41918) (8fdac8f)
  • compiler-cli: prefer non-aliased exports in reference emitters (#41866) (75bb931), closes #41443 #41277
  • compiler-cli: allow linker to process minified booleans (#41747) (1fb6724), closes #41655
  • compiler-cli: match string indexed partial declarations (#41747) (f885750), closes #41655
  • compiler-cli: update supported range of node versions to only include LTS versions (#41822) (5b463f4)
  • compiler-cli: autocomplete literal types in templates. (#41456) (#41645) (8b2b5ef)
  • compiler-cli: do not error with prepocessing if component has no inline styles (#41602) (a5fe8b9)
  • compiler-cli: ensure the compiler tracks ts.Programs correctly (#41291) (deacc74)
  • compiler-cli: prevent eliding default imports in incremental recompilations (#41557) (7f16515), closes #41377
  • compiler-cli: resolve rootDirs to absolute (#41359) (3e0fda9), closes #36290
  • compiler-cli: add useInlining option to type check config (#41043) (09aefd2), closes #40963
  • compiler-cli: readConfiguration existing options should override options in tsconfig (#40694) (b7c4d07)
  • compiler-cli: extend angularCompilerOptions in tsconfig from node (#40694) (5eb1954), closes #36715
  • compiler-cli: update ngcc integration tests for latest changes in rules_nodejs (#40710) (d7f5755)
  • compiler-cli: update type castings for JSON.parse usage (#40710) (b75d7cb)
  • core: do not retain dynamically compiled components and modules (#42003) (1449c5c), closes #19997
  • core: invoke profiler around ngOnDestroy lifecycle hooks (#41969) (e9ddc57)
  • core: AsyncPipe now compatible with RxJS 7 (#41590) (9759bca)
  • core: handle multiple i18n attributes with expression bindings (#41882) (73c6c64), closes #41869
  • core: update supported range of node versions to only include LTS versions (#41822) (f9c1f08)
  • core: detect synthesized constructors that have been downleveled using TS 4.2 (#41305) (274dc15), closes #41298
  • core: Switch emitDistinctChangesOnlyDefaultValue to true (#41121) (7096246)
  • core: remove duplicated EMPTY_OBJ constant (#41066) (bf158e7)
  • core: remove duplicated EMPTY_ARRAY constant (#40991) (e12d9de)
  • core: allow EmbeddedViewRef context to be updated (#40360) (a3e1719), closes #24515
  • core: make DefaultIterableDiffer keep the order of duplicates (#23941) (a826926), closes #23815
  • core: NgZone coaleascing options should trigger onStable correctly (#40540) (22f9e45)
  • elements: update supported range of node versions to only include LTS versions (#41822) (4f5d094)
  • elements: update type castings for JSON.parse usage (#40710) (efd4149)
  • forms: update supported range of node versions to only include LTS versions (#41822) (dc975ba)
  • http: complete the request on timeout (#39807) (61a0b6d), closes #26453
  • http: emit error on XMLHttpRequest abort event (#40767) (3897265), closes #22324
  • language-service: update supported range of node versions to only include LTS versions (#41822) (9b6198c)
  • language-service: use script versions for incremental compilations (#41475) (78236bf)
  • language-service: Only provide Angular property completions in templates (#41278) (0226a11)
  • language-service: Add plugin option to force strictTemplates (#41062) (e9e7c33)
  • language-service: use single entry point for Ivy and View Engine (#40967) (e986a97)
  • localize: relax error to warning for missing target (#41944) (35ceed2), closes #21690
  • localize: update supported range of node versions to only include LTS versions (#41822) (658ed1f)
  • localize: update type castings for JSON.parse usage (#40710) (4b469c9)
  • ngcc: detect synthesized constructors that have been downleveled using TS 4.2 (#41305) (8d3da56), closes #41298
  • platform-browser: prevent memory leak of style nodes if shadow DOM encapsulation is used (#42005) (d555555), closes #36655
  • platform-browser: update supported range of node versions to only include LTS versions (#41822) (ea05cfd)
  • platform-browser: configure XhrFactory to use BrowserXhr (#41313) (e0028e5), closes #41311
  • platform-browser: update type castings for JSON.parse usage (#40710) (7ecfd2d)
  • platform-browser-dynamic: update supported range of node versions to only include LTS versions (#41822) (bc45029)
  • platform-server: update supported range of node versions to only include LTS versions (#41822) (4b9d4fa)
  • router: update supported range of node versions to only include LTS versions (#41822) (0067edd)
  • router: Only retrieve stored route when reuse strategy indicates it should reattach (#30263) (a4ff071), closes #23162
  • router: recursively merge empty path matches (#41584) (1179dc8), closes #41481
  • router: fragment can be null (#37336) (b555160), closes #23894 #34197
  • router: update type castings for JSON.parse usage (#40710) (350dada)
  • service-worker: update supported range of node versions to only include LTS versions (#41822) (6b823d7)
  • service-worker: update type castings for JSON.parse usage (#40710) (4f7ff96)
  • upgrade: preserve $interval.flush when ngMocks is being used (#30229) (87dc851)
  • upgrade: update supported range of node versions to only include LTS versions (#41822) (10c4523)

By the way, did you notice that AsyncPipe has been patched by Ben Lesh to be compatible with both RxJS 6 and 7? How cool is that? ;-)

Breaking changes

As usual, there are a number of breaking changes with this release.

As the official release notes are pretty clear, I’m just copy/pasting those here:

  • Minified UMD bundles are no longer included in the distributed NPM packages.
  • animations: DOM elements are now correctly removed when the root view is removed. If you are using SSR and use the app's HTML for rendering, you will need to ensure that you save the HTML to a variable before destorying the app. It is also possible that tests could be accidentally relying on the old behavior by trying to find an element that was not removed in a previous test. If this is the case, the failing tests should be updated to ensure they have proper setup code which initializes elements they rely on
  • common: Methods of the PlatformLocation class, namely onPopState and onHashChange, used to return void. Now those methods return functions that can be called to remove event handlers
  • common: The methods of the HttpParams class now accept string | number | boolean instead of string for the value of a parameter. If you extended this class in your application, you'll have to update the signatures of your methods to reflect these changes
  • compiler-cli: Linked libraries no longer generate legacy i18n message ids. Any downstream application that provides translations for these messages, will need to migrate their message ids using the localize-migrate command line tool
  • core: Angular no longer maintains support for node v10, so if you're still using it for your development environment, it's really time to upgrade!
  • core: Previously the ng.getDirectives function threw an error in case a given DOM node had no Angular context associated with it (for example if a function was called for a DOM element outside of an Angular app). This behavior was inconsistent with other debugging utilities under ng namespace, which handled this situation without raising an exception. Now calling the ng.getDirectives function for such DOM nodes would result in an empty array returned from that function core: Switching default of emitDistinctChangesOnlyDefaultValue which changes the default behavior and may cause some applications which rely on the incorrect behavior to fail

emitDistinctChangesOnly flag has also been deprecated and will be removed in a future major release

The previous implementation would fire changes QueryList.changes.subscribe whenever the QueryList was recomputed. This resulted in an artificially high number of change notifications, as it is possible that recomputing QueryList results in the same list. When the QueryList gets recomputed is an implementation detail, and it should not be the thing that determines how often change event should fire.

Unfortunately, fixing the behavior outright caused too many existing applications to fail. For this reason, Angular considers this fix a breaking fix and has introduced a flag in @ContentChildren and @ViewChildren, that controls the behavior.

export class QueryCompWithStrictChangeEmitParent {
  @ContentChildren('foo', {
    // This option is the new default with this change.
    emitDistinctChangesOnly: true,
  })
  foos!: QueryList<any>;
}

For backward compatibility before v12 emitDistinctChangesOnlyDefaultValue was set to false. This change changes the default to true.

  • core: The type of the APP_INITIALIZER token has been changed to more accurately reflect the types of return values that are handled by Angular. Previously, each initializer callback was typed to return any, this is now Promise<unknown> | Observable<unknown> | void. In the unlikely event that your application uses the Injector.get or TestBed.inject API to inject the APP_INITIALIZER token, you may need to update the code to account for the stricter type.

Additionally, TypeScript may report the TS2742 error if the APP_INITIALIZER token is used in an expression of which its inferred type has to be emitted into a .d.ts file. To workaround this, an explicit type annotation is needed, which would typically be Provider or Provider[].

  • core: Minimum supported zone.js version is 0.11.4. Thus it means that if you're using an older version, it's also time to upgrade zone.js in your project!
  • forms: The emitEvent option was added to the following FormArray and FormGroup methods:
  • FormGroup.addControl
  • FormGroup.removeControl
  • FormGroup.setControl
  • FormArray.push
  • FormArray.insert
  • FormArray.removeAt
  • FormArray.setControl
  • FormArray.clear

If your app has custom classes that extend FormArray or FormGroup classes and override the above-mentioned methods, you may need to update your implementation to take the new options into account and make sure that overrides are compatible from a types perspective.

  • forms: Previously min and max attributes defined on the <input type="number"> were ignored by Forms module. Now presence of these attributes would trigger min/max validation logic (in case formControl, formControlName or ngModel directives are also present on a given input) and corresponding form control status would reflect that.
  • platform-browser: XhrFactory has been moved from @angular/common/http to @angular/common.

Before

import { XhrFactory } from '@angular/common/http';

After

import { XhrFactory } from '@angular/common';
  • router: Strict null checks will report on fragment potentially being null. Migration path: add null check.
  • router: The type of the RouterLinkActive.routerLinkActiveOptions input was expanded to allow more fine-tuned control. Code that previously read this property may need to be updated to account for the new type.

Updated roadmap

According to the current Angular roadmap, the team is now busy with the following improvements:

  • Improving developer experience while debugging and profiling. This should help us to understand the component structure (I imagine like React Dev Tools allow for React), and change detection
  • Improving test times and debugging with automatic tear down: This should improve the isolation between tests and test times. The TestBed will also be changed to automatically clean up and tear down the test environment after each test is executed
  • Using ES2017+ as the default output: They'll identify roadblocks and remove those so that the default output language can be upgraded
  • Integrating MDC Web into Angular Material
  • Improving the accessibility of Angular Material components
  • Publishing guides about advanced concepts such as change detection, performance profiling, interactions with Zone.js, etc
  • Updating the e2e testing strategy (cfr above)
  • Preparing the upgrade to RxJS v7+. As you might know, RxJS 7 has been released recently. Hopefully we should soon be able to upgrade

In the future, the Angular team plans to:

  • Look into micro frontend architecture: they might introduce means for us to easily create micro frontends using Angular
  • Improve developer experience with strict typing for Angular forms (we so desperately need this)
  • Make Zone.js optional, which should simplify the framework, improve debugging, reduce bundle sizes and even allow taking advantage of the native async/await syntax
  • Improve build performance by integrating the Angular compiler (ngc) as a TypeScript compiler plugin
  • Allow adding directives to host elements. This has been requested by the community for a long time too!
  • Make NgModules optional to ease the learning curve
  • Provide us with easier means to implement code-splitting at the component-level

Angular Material & Angular CDK

Sass migration

Internally, both Angular Material and the CDK have migrated to the new Sass module system.

If your application uses either of those, then you'll need to make sure that you've replaced node-sass by sass: https://www.npmjs.com/package/sass. The node-sass package is not maintained anymore!

With this migration, the Sass theming API has been enhanced, and it now allows us to take advantage of Sass's @use utility.

There's now a single entry point for @angular/material and @angular/cdk.

Finally, the APIs have also been changed for clarity. Many functions, mixins and variables have been renamed along the way.

You can find more information about these changes in the new theming guide: https://github.com/angular/components/blob/master/guides/theming.md. In addition, note that the guides on https://material.angular.io have been rewritten to showcase the new API, and include explanations.

The upgrade process will automatically migrate code to the new Sass API. You can find before/after examples here.

Angular CDK changes

Version 12 includes a number of changes to the Angular CDK.

Here, I'll only list the new features, but you can check out the release notes if you want details about the bug fixes and performance improvements:

  • Drag-drop: The dropped event now includes a dropPoint property, determining the exact point where the mouse pointed was when the item was dropped. More info here
  • Drag-drop: The preview container can now be customized
  • Table: A new directive allows to enable the recycle view repeater, which caches disposed rows and reuses them when the dataset changes. This can help improve performances (reduce latency)
  • Table: Added the offsets of sticky elements to the StickyUpdate interface
  • Stepper: When a user tries to move away from a step, an interacted event will now be emitted
  • Stepper: The orientation can now be changed
  • Accessibility: A FocusOptions object can now be passed into the various focus trap methods
  • Testing: New WebDriver harness environment. I haven't dived into this one yet so I can't tell you more. Check out the PR

Angular Material changes

There are also a number of changes for Angular Material. Again, check out the release notes for the full list of bug fixes.

New features:

  • Date picker: No longer depends on dialog
  • Stepper: The orientation can now be changed dynamically (cfr similar change in the CDK)
  • Stepper: Allow for content to be rendered lazily
  • Menu: Allow updating the menu position programmatically
  • Mat error: Now uses aria-live="polite" instead of role="alert". More details here
  • Tabs: Add method to programmatically set focus on a specific tab
  • List: Now returns an array with changed options from the selectAll and deselectAll methods. Check out the PR for details
  • Slide toggle: Allow to globally configure a default color through a provider
  • Tooltip: Now exposes the effective position of the tooltip through a class on the component
  • Radio, Checkbox and Slider: Include the background color of those components in the print stylesheets

There are also a number of changes on the experimental version: https://github.com/angular/components/blob/master/CHANGELOG.md#material-experimental.

Angular Universal

Angular Universal 12 is also fresh out of the oven.

With this release, Universal now inlines critical CSS by default, which is pretty cool.

Universal now includes a new proxyConfig option to provide custom proxy configurations to the ssr-dev-server builder.

In this version, there's a new (experimental) SSR engine called Clover (reminds me of a quality tool in the Java ecosystem). This new engine seems promising. It aims to simplify things, get us rid of the Window is undefined error, remove the need for multiple builds for SSR/prerender, generate application shells without an extra build, and more. We'll probably hear more about it later on. If you're curious, then check out the PR.

This version includes a builder that can be used to generate static pages (i.e., pre-rendering) using the new Universal Clover approach.

Finally, this version also includes TLS support for the dev-server.

Google Maps

As you know, Angular includes components for Google Maps and Youtube. Version 12 brings a few improvements for the Google Maps component:

  • Now includes an icon input to easily customize the marker
  • Now emits a clusterClick event when a cluster has been clicked
  • Includes a wrapper around the Google Maps Geocoder API, and exports MapDirectionsResponse, which was not exposed before
  • Introduces support for rendering heatmaps
  • Added MapDirectionsRenderer, allowing to render directions on a map, and MapDirectionsService, which wraps google.maps.DirectionsService to calculate routes between two points.
  • New options input on the marker clusterer, similar to the other directives

Upgrading

As usual, there's a complete upgrade guide available, and ng update will help you out: https://update.angular.io/?l=3&v=11.0-12.0

If you're using Nrwl NX (you really should), note that Nx 12.3 includes support for Angular 12 already! Learn more about that here. As an added benefit, Nx will also help you migrate from TSLint to ESLint (and it's time!)

Conclusion

In this article, I've explored the new features of Angular 12.

As usual, this is release is heroic (what else could it be?! 😎).

Ivy is moving forward, and hopefully, we'll "soon" be less annoyed by ngcc, as the ecosystem migrates. There are a number of awesome changes with this release, so check it out and upgrade now!.

That's it for today! ✨

About Sébastien

Hello everyone! I'm Sébastien Dubois. I'm an author, founder, and CTO. I write books and articles about software development & IT, personal knowledge management, personal organization, and productivity. I also craft lovely digital products 🚀

If you've enjoyed this article and want to read more like this, then become a subscriber, check out my Obsidian Starter Kit, the PKM Library and my collection of books about software development 🔥.

You can follow me on Twitter 🐦

If you want to discuss, then don't hesitate to join the Personal Knowledge Management community or the Software Crafters community.