Bjeaurn

Upgrading to Angular 9: My experience

– 5 minutes read – Angular

Last week, version 9 of Angular was released. The much anticipated Ivy Renderer was now set to default. The promise of smaller bundle sizes and faster applications was just too much for me to pass over. So Monday first thing, I wanted to get my upgrade going.

The following story shares my experiences in upgrading a sizable application with some complexity, giving a good indication of the types of issues you may be running into with your application.

Upgrading

As any upgrade starts, I created a new branch and ran the usual ng update to see what versions are eligible for automatic upgrades. I tried to run a ng update —all which failed cause of dependency issues like TSLint, Codelyzer and rxjs-tslint-rules not being updated yet. Normally, you could go for a ng update —all —force and see if that sticks, but considering the complexity of our application, I prefer to do it a bit more manually.

ng update @angular/cli @angular/core

This works fine, updates the CLI first and then updates and runs all migrations for the Angular application. This may result in some compilation errors, as not all situations and edge cases can be taken care of in the automatic migrations. Especially in larger applications, you’ll run into some edge cases.

An interesting error I ran into had nothing to do with the complexity of our application

ERROR in node_modules/@types/node/index.d.ts:200:11 - error TS2300: Duplicate identifier 'IteratorResult'.
    
    200 interface IteratorResult<T> { }
                  ~~~~~~~~~~~~~~
    
      node_modules/typescript/lib/lib.es2015.iterable.d.ts:41:6
        41 type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
                ~~~~~~~~~~~~~~
        'IteratorResult' was also declared here.

I’m still not entirely certain what exactly caused this compilation error after running the upgrade, it may be related to the Angular CLI as it depends on a newer version of Node. Fixing this issue turned out to be as easy as npm install --save-dev @types/node or updating your @types/node reference in your package.json.

Now that that’s sorted, and now we can move on to actually application-related compilation issues.

ERROR in Unable to write a reference to LabelCheckboxComponent in /dev/example/ng-app/src/app/shared/checkbox/checkbox.component.ts from /dev/example/ng-app/src/app/shared/checkbox/checkbox.module.ts

I searched around for this error, to see what it was referring to. I ran into a Github issue and commit that helped me isolate the issue.

As it turns out, this issue is caused by having a rootDirs: [] or rootDirs: {} in your tsconfig.json, that had any reference to ./src/. Removing this solves the error.

Testing

In any sizeable application, having a base of tests to guarantee the function of your application is crucial. Considering we have automated this entire process, we don’t want any flakiness or unstable tests as a result of an upgrade or migration, so making sure all our tests operated as normal was the next priority after the application compiled and booted.

As it turns out, the “older” way of unit testing was outdated and no longer functioned. In its original form, it looked a bit like this:

const testBed = getTestBed()
testBed.configureTestingModule({
    imports: [HttpClientTestingModule, MatIconModule]
})
testBed.initTestEnvironment(
    BrowserDynamicTestingModule,
    platformBrowserDynamicTesting(),
)

The TestBed has seen a complete overhaul, and is no longer supposed to be instantiated before use. More importantly, the TestBed needs to be initialized before any configureTestingModule({}) can be called. The errors we got for this were a little cryptic, but our solution looks like this:

TestBed.initTestEnvironment(
    BrowserDynamicTestingModule,
    platformBrowserDynamicTesting(),
)
TestBed.configureTestingModule({
    imports: [HttpClientTestingModule, MatIconModule]
})

Then there was the issue of TestBed.get(SYMBOL_TO_INJECT) being deprecated in favor of the more strongly typed TestBed.inject<T>(). Unfortunately at the time of writing, there was no automated migration path for this. This meant a manual find & replace of TestBed.get with TestBed.inject<any> and then going over all replaced cases to add the proper expected type. This took maybe 30 minutes and turned out to be a simple issue to solve.

This was the end of my compile issues and the tests started running and functioning again. Of course, some tests failed because of the stricter compilation of HTML templates, so the unit tests pointed out a few flaws in our application. The stricter checking of templates, in my opinion, is a very good thing!

For E2E testing we use Testcafé, which just ran as we had left it. Of course, the application needs to compile and start before it does.

Continuous Integration (CI)

Of course, this whole application had to run in our CI environment too. So after checking all the changes necessary to upgrade and get compilation going, it was pushed to CI for checking.

This brought us to the new ngcc throwing some errors like:

Error: ngcc is already running at process with id 4968.

This is because we run our unit tests, E2E tests and things like Sonar and other quality gates in parallel pipelines. I also ran into it on my local development machine when I had a ng serve running in one terminal, and tried to run ng update in another.

This error points to ngcc doing some grunt work on demand, where you can have a postinstall script in your package.json compiling some framework related things ahead of time.

"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",

After adding this to the package.json scripts part and running npm install, you’ll see the compiler pre-compile some things for later reference. This speeds up the build process and allows the application to run multiple processes in parallel.

When this was checked in, the CI started functioning as expected and the migration was completed.

First experiences with Angular 9

After the upgrade, the CI deployed our new version to a development environment for sanity checking. The first thing that I noticed was how snappy the application felt. It wasn’t necessarily slow before, but it just felt faster and more responsive. The bundle sizes weren’t necessarily noticeably a lot smaller, but that’s on par with the complexity and size of our application.

Honestly, I’m happy and thrilled with our upgrade experience and the release as a whole. I’d like to congratulate and thank the Angular team for delivering yet another stellar version and living up to the hype.