Real-World Angular Ivy Upgrade (v9-next)

ngconf

ng-conf

Posted on June 11, 2021

Real-World Angular Ivy Upgrade (v9-next)

Jared Youtsey | ng-conf | Oct 2019

I recently published an article about the compatibility opt-in preview version of Ivy in version 8 and what I experienced attempting to upgrade. This is a follow-on to show what the current upgrade path looks like using Angular v9.0.0-next.6 pre-beta, which contains optimizations for bundle size and performance as well as some bug fixes that were not present in v8’s preview.

As a reminder, Ivy is not ready for production. This is still a compatibility opt-in preview. Use at your own risk. Your mileage may vary.

I’ll be upgrading a large commercial application that leverages many third-party dependencies and a wide gamut of the Angular framework.

The initial step is to upgrade Angular:

ng update @angular/cli@next @angular/core@next
Enter fullscreen mode Exit fullscreen mode

On first attempt I ran into a dependency that specifies an Angular version < 9:

Incompatible peer dependencies found.
Peer dependency warnings when installing dependencies means that those dependencies might not work correctly together.
You can use the '--force' option to ignore incompatible peer dependencies and instead address these warnings later.
Enter fullscreen mode Exit fullscreen mode

To get around this we can use the --force flag:

ng update @angular/cli@next @angular/core@next --force
Enter fullscreen mode Exit fullscreen mode

This seemed to compile, but scrolling through the output I saw this:

This migration uses the Angular compiler internally and therefore 
projects that no longer build successfully after the update cannot 
run the migration. Please ensure there are no AOT compilation 
errors and rerun the migration.. The following project failed: 
src/tsconfig.app.json

            Error: error TS100: Couldn't resolve resource 
../../assets/scss/common/component.common from 
/.../src/app/common/app-header.component.scss
Migration can be rerun with: "ng update @angular/core --from 8.0.0 
--to 9.0.0 --migrate-only"
            Successfully migrated all found undecorated classes
            that use dependency injection.
Enter fullscreen mode Exit fullscreen mode

This is a bug that has been fixed since writing this article. If you’re curious about it, keep reading. If not, skip to the next section.

The problem was with an SCSS @import statement:

@import '../../assets/scss/common/component.common';
Enter fullscreen mode Exit fullscreen mode

The new compiler is much more strict. The actual file name is needed. If your files have an underscore at the beginning you may have been not including those either. In all of my SCSS imports I had to explicitly use the correct file name, in this case component.common.scss. I had to run the command over and over, fixing imports until it finally continued past this point.


Now, before I continue, I’m going to upgrade everything I logically can in my package.json to be sure all of my dependencies are as up to date as I can get them. I use a Visual Studio Code Extension called Version Lens to help manage my package.json.

Screenshot reading "Versions Lens 0.24.0 - Shows the latest version for each package using code lens pflannery".

Version Lens annotates each item in package.json with the current version specified and the latest available on npm.

Screenshot of a section of code.

Clicking on the “latest” link will update my package.json to that version. I updated pretty much everything to latest, with the exception of @types/node since I want that to match my version of node. Do not upgrade typescript beyond 3.5.x. If you do you won’t be able to compile with a cryptic “ERROR in TypeError: Cannot read property ‘kind’ of undefined” error. Angular doesn’t yet support TypeScript 3.6.

npm install
Enter fullscreen mode Exit fullscreen mode

So far, so good.

ng serve
Enter fullscreen mode Exit fullscreen mode

Uh, oh

ERROR in The ngcc compiler has changed since the last ngcc build.
Please completely remove the "node_modules" folder containing "/Users/jyoutsey/src/MyMedstudy/ng/node_modules/hammerjs" and try again.
Enter fullscreen mode Exit fullscreen mode

This isn’t actually an error, per se. Third party npm modules are not compiled to be compatible with Ivy. So, we either have to A) run ivy-ngcc against node_modules, which will compile them for compatibility, or B) delete node_modules and do another npm install, then ng build, as ng build and ng serve will both execute the build target which will run ivy-ngcc for you. I prefer B for the simple reason that I think deleting node_modules now and then is a good thing. If you wish to do A then feel free. You’ll have to do B if you’re switching back and forth between Ivy-disabled branches and Ivy-enabled branches.

delete node_modules
npm install
ng serve
Enter fullscreen mode Exit fullscreen mode

Here we start getting into some very detailed error messages:

ERROR in app/common/global-loading-indicator.component.ts:12:3 - error TS2554: Expected 2 arguments, but got 1.
12  @ViewChild('inner') inner;
     ~~~~~~~~~~~~~~~~~~
../node_modules/@angular/core/core.d.ts:7929:47
    7929     (selector: Type<any> | Function | string, opts: {
                                                       ~~~~~~~
    7930         read?: any;
         ~~~~~~~~~~~~~~~~~~~
    7931         static: boolean;
         ~~~~~~~~~~~~~~~~~~~~~~~~
    7932     }): any;
         ~~~~~
    An argument for 'opts' was not provided.
Enter fullscreen mode Exit fullscreen mode

Now, this application is already on Angular version 8. And it’s building constantly without these errors. When we initially went from version 7 to version 8 we had to update some static flags, but only if it were static: true. Now, the opts is required and we must fill in the static: false. Depending on when you migrated from 7 to 8 you may not have to do this because at some point this became mandatory for both true and false.

The fix for these is fairly simple, but you’ll have to work through each one and provide the second argument:

@ViewChild('selector', { static: true/false })
Enter fullscreen mode Exit fullscreen mode

As much as I appreciate that these error messages are very clear and helpful, they are not “linked” so that I can Cmd/Ctrl + Click to navigate to the offending file.

The simple answer to “should static be true or false?” is “if the item being queried for has or is in an *ngIf or *ngFor, then static should be false.”

<div *ngIf="...">
  <!-- If querying for this div or anything contained in this div
       then { static: false } -->
</div>
<div *ngFor="...">
  <!-- If querying for this div or anything contained in this div
       then { static: false } -->
</div>
Enter fullscreen mode Exit fullscreen mode

You’ll have to assess your code and template to determine on a case-by-case basis which is correct. All but one of mine turned out to be false.

Here is the migration guide officially discussing this issue.


ng serve
Enter fullscreen mode Exit fullscreen mode

Now I have some errors that I also saw in the version 8 preview.

ERROR in app/common/searchable-select.component.ts:27:4 - error 
TS1117: An object literal cannot have multiple properties with the 
same name in strict mode.
27              [disabled]="disabled"
                ~~~~~~~~~~~~~~~~~~~~~
Enter fullscreen mode Exit fullscreen mode

This is a case where you cannot have a direct class binding and an attribute binding with the same name, i.e.:

<app-some-component 
  [class.disabled]="value" 
  [disabled]="value">
</app-some-component>
Enter fullscreen mode Exit fullscreen mode

I have it on authority that this is a bug, but I won’t name names. That said, the fix is really simple.

<app-some-component 
  [ngClass]="{ disabled: value }" 
  [disabled]="value">
</app-some-component>
Enter fullscreen mode Exit fullscreen mode



ng serve
Enter fullscreen mode Exit fullscreen mode

Uh oh… Now I’ve got a good one. And I plead innocence. I inherited this code base and haven’t been through it 100% over the past year. But we have a base @Directive that @Components are derived from. My initial hunch is that the problem is that the @Directive just needs converted to an @Component. Here is the error:

ERROR in app/features/.../derived.component.ts:34:4 - error 
TS8002: 'stepNumber' is not a valid property of <app-derived>.
34              [stepNumber]="getAdjustedStepNumber(2)"
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Enter fullscreen mode Exit fullscreen mode

For more information on base classes for components and directives and how Ivy will handle migration as of right now, please reference https://next.angular.io/guide/migration-undecorated-classes. As of writing this article, Ivy did not migrate this correctly for me.

@Input() stepNumber is defined on the base component, not the derived, so Ivy is unhappy. Sure enough, changing the @Directive() to the following will fix the compilation error. (But, generally speaking, favor composition over inheritance. This pattern would not be best practice, in my opinion.)

@Component({
  selector: `app-base`,
  template: ``
})
Enter fullscreen mode Exit fullscreen mode



ng serve
ERROR in ./src/polyfills.ts
Module not found: Error: Can't resolve 'core-js/es7/array' in 
'/myApp/src'
Enter fullscreen mode Exit fullscreen mode

I have to support IE which means I use conditional polyfills. However, one polyfill that seems to be missing is for Array. I can’t say I completely understand why I need to include this one and not others. But with the latest version of core-js the version is not present in the path:

Edit polyfills.ts to remove the version:

import 'core-js/es/array';
Enter fullscreen mode Exit fullscreen mode

At this point, my app compiles! Celebration time!

Image of fireworks against a black sky.

Now that you have it building, you should re-run the migration. Run ng update again to ensure that your migration is complete. I was so caught up in the details at this point that I forgot to do this and ended up dealing with some of these things manually. Be aware of that as you continue reading…

I do have some warnings left over:

WARNING in /myApp/src/app/common/interfaces/contentSpecialty.ts is 
part of the TypeScript compilation but it's unused.
Add only entry points to the 'files' or 'include' properties in 
your tsconfig.
Enter fullscreen mode Exit fullscreen mode

This appears to be a bug and I’ve opened an issue with the Angular team. It is just a warning, so it’s not a show-stopper. The source of this warning is that the compiler is identifying an unused interface. Except, this interface is used. It is part of a data structure received through an API call. So, it’s referenced in a parent interface that is used in the project. But this interface is never assigned to or leveraged by the Angular code directly. One way to solve this error would be to move this interface into the same file as the parent interface, or to just in-line the interface into the parent interface.

I’m going to take the easier approach and just ignore it.


A quick test through my application and things seem to be working as expected. Of course, I would want to do a full regression test to ensure nothing odd is broken.

But what about bundle sizes and performance?

Image for post

Comparison of pre/post Ivy build module sizes.

Well, the news isn’t great. For example, the main-es2015 bundle is 891kB in Angular version 8. But in v9 we have 2.03MB! Overall the Ivy build was larger by 1.45MB. By the writing of this article the CLI has advanced to 9.0.0-next.9, which is starting to add more of the optimizations to improve bundle sizes. Remember, this is an opt-in preview, not final shipping code. The Angular Team is still hard at work in this area.

As for performance, as a human being, I didn’t notice Ivy being any more/less performant. I’m sure I could profile it, but the reality was, I didn’t notice a difference. Each app will have different requirements on this front, so you’ll have to test your own application’s performance under v9 and Ivy.

As a reminder, Ivy is still not ready for production, and neither is Angular v9 yet. But now you have a feel for how much work there really is to get up and going with Ivy. Of course, you may have other third-party dependencies that just won’t work, or parts of the application that are leveraging something that we are not, so you may have some different experiences.

Please, open issues when you run into problems trying out Ivy. The Angular team is working hard to make the version 9.0.0 update a smooth one.

Be careful out there!


ng-conf: Join us for the Reliable Web Summit

Come learn from community members and leaders the best ways to build reliable web applications, write quality code, choose scalable architectures, and create effective automated tests. Powered by ng-conf, join us for the Reliable Web Summit this August 26th & 27th, 2021.
https://reliablewebsummit.com/

💖 💪 🙅 🚩
ngconf
ng-conf

Posted on June 11, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related