Juri Strumpflohner

RSS

Update to Angular Version 8 now!

Author profile pic
Juri Strumpflohner
Published

Let’s dive into the latest Angular version 8 that just got released. We’ll quickly explore what’s new, why you should update, how that works and what you should watch out for.

Before you start, there’s also an official post about the release from the Angular team:

What’s new? What to expect

So what’s new in v8. Many of the things have already been announced at this year’s NGConf and I’ve blogged about it here:

Here are the main key points:

  • Differential loading - This feature produces separate bundles for legacy and evergreen browsers,thus reducing the amount of polyfills that have to be downloaded (~2-10% smaller bundles).
  • Builders and Deploy - If you ever looked at the angular.json you may have notices the builder API (also known as Angular Architect). While it could already be used (i.e. ngx-build-plus), in v8 the API stabilized and gives 3rd party tooling to hook into the Angular build process. Moreover related to this change, the team also collaborated with the major cloud providers to integrate automated deployment of Angular apps to their respective infrastructure.
  • Web Workers - Angular 8 now allows you to offload heavy work into a separate web worker, thus freeing up the main thread. This allows to further improve the speed and even execute things in parallel in the browser.
  • Service Worker Improvements
  • Code-splitting via import - import() can now be used to execute code splitting
  • AngularJS $location support - Some enhancements have been made to provide better integration with the AngularJS $location service in hybrid (AngularJS / Angular) apps.
  • Better IDE completion
  • Simplified Getting Started Guide - The team has worked hard as well to simplify the “Getting Started” Guide and help new people onboard.

But wait :thinking:, where’s Ivy? Right! Angular v8 ships with Ivy, however it won’t be enabled by default, yet. The team has enabled Ivy within Google but wants to take another 6 months till v9 to get feedback from community projects. You can see Angular 8 as the transition to full, default Ivy support.
That said, you can totally enable Ivy. Here’s a guide how to do that.

$ ng new my-app --enable-ivy

Or for an existing app, by adding the enableIvy option to the tsconfig.app.json.

{
  "compilerOptions": { ... },
  "angularCompilerOptions": {
    "enableIvy": true
  }
}

If you enable Ivy and encounter any kind of issues, let me know!

Another experimental feature is Bazel. Bazel is Google’s open source variant of their internal general purpose build system that powers their entire infrastructure. The Angular team jumped onto this bandwagon with https://bazel.angular.io/, providing automated Bazel builds via the Angular CLI. Bazel is made to scale via

  • incremental builds (sharing cache between team and CI)
  • full-stack support
  • scale on the cloud

…and comes with some really good first performance improvement results

You can opt-into Bazel via

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

Stay up to date! Evergreen Angular

It’s again time for a new release. As you probably know, when the Angular team started with “the new Angular”, one of the promises was to be very serious about semver when releasing new versions (something that wasn’t the case with Angular v1). Having such “contract” allows us to seamlessly upgrade minor and patch versions throughout the year. At the same time, however, this also means that at some point there will be breaking changes that are necessary to have the framework evolve.

Currently we have the following release schedule

  • patch releases every week
  • ~3 monthly minor releases after each major release
  • a major release every 6 months (usually September/October & April/May)
  • 12 months long-term support (LTS)

Two major releases a year also means two potentially breaking changes, right? While this might seem to be an issue to worry about, it is not, thx to automated migration scripts. The team behind Angular is very cautious about breaking changes. One of the reasons being that they serve Google-internal teams and inside Google, breaking something means fixing it. You can imagine that updating hundreds of apps is not scalable. That’s why the awesome tooling behind the Angular CLI provides “schematics”, “intelligent” scripts that are executed during the upgrade process and modify your codebase to automatically fix breaking changes. Have a look in the video below to see that in action.

That said, it’s extremely important to keep your app up-to-date with the latest Angular version. Upgrading step by step will be much easier than not upgrading for years and then have to upgrade over multiple major versions.

How to update

When it comes to the update process itself, the update.angular.io site is extremely helpful.

The site allows you to choose the start and target version and then gives you a set of instructions on how to update your codebase.

Check out the video below where I update an Angular Material based project from v7 to v8.

What to watch out for

Here are some of the things you might immediately notice at the code level.

Lazy routes

A couple of weeks ago I published an article about the various possibilities we have to apply lazy loading in Angular. You can find it here:

One of them is via routing, and I’m pretty sure you’ve seen that already:

const routes: Routes = [
  {
    path: 'issues',
    loadChildren: './issue-list/issue-list.module#IssueListModule',
  }
];

So far, what we’ve been using in Angular is this “magic string” passed to loadChildren, which was in turn picked up by the Angular CLI to know where to apply code splitting and load the JS file at runtime. Starting with v8, this magic string is no more necessary and has been aligned with the standard way of doing lazy loading across the various tools, which is by simply use an import() statement. As a result, we can now write the lazy route like

const routes: Routes = [
  {
    path: 'issues',
    loadChildren: () => import('./issue-list/issue-list.module').then(m => m.IssueListModule)
  }
];

Note that when updating via the Angular CLI, the schematics will automatically transform your existing loadChildren to the new syntax :tada:.

Queries

When talking about queries, we mean

  • @ViewChild
  • @ContentChild,
  • @ViewChildren (not affected by the upgrade)
  • @ContentChildren (not affected by the upgrade)

When upgrading to v8, there’s a small breaking change related to the @ViewChild and @ContentChild. The team tried to avoid it, but such things happen.

During the upgrade you will get a notification with a link to more details.

You can read up all the details at https://angular.io/guide/static-query-migration.

If the Angular CLI is not able to automatically infer whether to use the static or dynamic resolution, it’ll add a corresponding comment and warning on the console

To summarize, what is it all about?

Assume you have the following:

<div foo></div>

In your code you’d use a @ViewChild such as

@ViewChild(Foo) foo: Foo;

(where Foo is some Angular directive)

Usually it’s always safe to assume foo will be populated after the ngAfterViewInit (or ngAfterContentInit for content queries with @ContentChild). However, some of them were also accessible already in the onInit directly. The reason is that the compiler behind the scenes categories them in

  • static queries available immediately
  • dynamic queries available only at runtime

Our code example above would be an example of a static query because <div foo> it is immediately available. We could safely access it in the ngOnInit. On the other side, assume we change the code like

<div foo *ngIf="isVisible"></div>

In such case, it would only become available once isVisible evaluates to true, which might happen at any time while executing the app. Such queries are dynamic queries.

The main problem is that this wasn’t explicit. Hence, when upgrading to v8, the code migration will automatically transform your code to

// query results available in ngOnInit
@ViewChild('foo', {static: true}) foo: ElementRef; 

// query results available in ngAfterViewInit
@ViewChild('foo', {static: false}) foo: ElementRef;

TypeScript upgrade

By upgrading to Angular 8 you’ll also upgrade to TypeScript 3.4. If you’re curious about the new features here’s the corresponding documentation.

As a result after the upgrade (even though that completes successfully), you may get errors. Most probably they are due to better type inference which reveal new potential typing issues.

Other Deprecations

Check out the new deprecation guide on the official site. Still have questions? Open an issue in the Angular CLI repository if it’s related to the upgrade or on the Angular repository if it’s framework related. Or simply ping me on Twitter :smiley:

FAQ - Potential upgrade issues

Rerun migrations

What if you did the Angular upgrade, but for whatever reason some of the code transformations didn’t complete successfully. You end up having Angular 8 (or whatever version you’re upgrading) already in your node_modules folder and package.json.

Generally speaking, my suggestion is to use Git. Create a migration branch, which allows you to easily go back and forth during the upgrade. Commit after each step so you’ve got a backup on the way.

Other than that, the Angular CLI also gives you the possibility to run the migration again, even though you already have the latest version in your package.json. Just execute

// re-run CLI schematics
$ ng update @angular/cli --from 7 --to 8 --migrate-only

// re-run Angular core schematics
$ ng update @angular/core --from 7 --to 8 --migrate-only

Material Upgrade: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

When I upgraded Angular material with ng update @angular/material on our fairly large monorepo, I got the following exception.

<--- Last few GCs --->

[85884:0x103802200]   712051 ms: Scavenge 2004.6 (2047.9) -> 2004.5 (2047.9) MB, 4.1 / 0.0 ms  (average mu = 0.199, current mu = 0.181) allocation failure
[85884:0x103802200]   712072 ms: Scavenge 2006.3 (2048.9) -> 2004.6 (2048.4) MB, 3.8 / 0.0 ms  (average mu = 0.199, current mu = 0.181) allocation failure
[85884:0x103802200]   712077 ms: Scavenge 2005.6 (2049.4) -> 2005.6 (2049.9) MB, 4.3 / 0.0 ms  (average mu = 0.199, current mu = 0.181) allocation failure


<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x100e146e6]
Security context: 0x08b76239a2f1 <JSObject>
    1: stringSlice(aka stringSlice) [0x8b725f97839] [buffer.js:~568] [pc=0x1d077761a16a](this=0x08b76f0804d1 <undefined>,0x08b765580f19 <Uint8Array map = 0x8b742025759>,0x08b786894e49 <String[#4]: utf8>,0,1073870)
    2: toString [0x8b7623f02f9] [buffer.js:~622] [pc=0x1d0777ee3789](this=0x08b765580f19 <Uint8Array map = 0x8b742025759>,0x08b786894e49 <String[#4]:...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x100075bd5 node::Abort() [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 2: 0x100076316 node::errors::TryCatchScope::~TryCatchScope() [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 3: 0x1001697d7 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 4: 0x10016976c v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 5: 0x1005480d5 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 6: 0x1005491c3 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 7: 0x100546bc3 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 8: 0x10054487f v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/jstrumpflohner/.nvm/versions/node/v12.4.0/bin/node]
 ...
[1]    85884 abort      ng update @angular/material @angular/cdk @angular/cdk-experimental

This is a common issue when the node process needs more memory. To solve this, pass the max_old_space_size option to the node process, like this:

$ node --max_old_space_size=8000 ./node_modules/.bin/ng update @angular/material @angular/cdk

Ngrx: Type ‘Observable<AppConfigLoaded>’ is not assignable to type ‘Observable<Action>’

Another strange error I got when upgrading my NX based monorepo from v7 to v8 was the following:

ERROR in libs/r3-core/src/lib/+state/app-config/app-config.effects.ts(22,5): error TS2322: Type '(action: LoadAppConfig, state: AppConfigPartialState) => Observable<AppConfigLoaded>' is not assignable to type '(a: LoadAppConfig, state?: AppConfigPartialState) => void | Action | Observable<Action>'.
  Type 'Observable<AppConfigLoaded>' is not assignable to type 'void | Action | Observable<Action>'.
    Type 'Observable<AppConfigLoaded>' is not assignable to type 'Observable<Action>'.
      Types of property 'source' are incompatible.
        Type 'import("/Users/jstrumpflohner/myapp/node_modules/rxjs/internal/Observable").Observable<any>' is not assignable to type 'import("/Users/jstrumpflohner/myapp/node_modules/@nrwl/angular/node_modules/rxjs/internal/Observable").Observable<any>'.
          Types of property 'operator' are incompatible.
            Type 'import("/Users/jstrumpflohner/myapp/node_modules/rxjs/internal/Operator").Operator<any, any>' is not assignable to type 'import("/Users/jstrumpflohner/myapp/node_modules/@nrwl/angular/node_modules/rxjs/internal/Operator").Operator<any, any>'.
              Types of property 'call' are incompatible.
                Type '(subscriber: import("/Users/jstrumpflohner/myapp/node_modules/rxjs/internal/Subscriber").Subscriber<any>, source: any) => import("/Users/jstrumpflohner/myapp/node_modules/rxjs/internal/types").TeardownLogic' is not assignable to type '(subscriber: import("/Users/jstrumpflohner/myapp/node_modules/@nrwl/angular/node_modules/rxjs/internal/Subscriber").Subscriber<any>, source: any) => import("/Users/jstrumpflohner/myapp/node_modules/@nrwl/angular/node_modules/rxjs/internal/types").TeardownLogic'.
                  Types of parameters 'subscriber' and 'subscriber' are incompatible.
                    Property '_parentOrParents' is missing in type 'Subscriber<any>' but required in type 'Subscriber<any>'.

This seems due to an incompatibility between RxJS and Ngrx v7 in a certain version. Upgrading to Ngrx v8 might solve the issue (I didn’t try though). In my case downgrading RxJS to ~6.4.0 helped.

$ yarn add rxjs@~6.4.0

or

$ npm i rxjs@~6.4.0 --save