Automatically upgrade lazy-loaded Angular modules for Ivy!
Tags: angular, javascript, lazyloading, automation
Published: 2019-10-27 17:00:33

(originally posted on [Angular in Depth](https://blog.angularindepth.com/automatically-upgrade-lazy-loaded-angular-modules-for-ivy-e760872e6084))

##Updated 29/05/2019 — ✨

Thanks to the changes [in Angular 8](https://blog.angular.io/version-8-of-angular-smaller-bundles-cli-apis-and-alignment-with-the-ecosystem-af0261112a27), we can now use the [**`import()`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports) operator to fetch a module as we navigate around our application.

The migration described below got merged into the Angular CLI, so you no longer need to use the lint rule I created. You can just follow the normal `ng update` process, and your code will be migrated to the new format. Thanks to other changes by the Angular team, it is even backwards compatible so you can use `import` without Ivy!

I’ve left the original article here for reference. Enjoy!

___

## Lazy-loading in Angular before v8.0.0! 💾

If you ever created a lazy-loaded module in an Angular app before v8.0.0, then the following code might look pretty familiar to you:

{% gist https://gist.github.com/phenomnomnominal/10aee045be75004c1d06482d00b37624 %}

It did the job, but it was fairly magical, and it relied on a special string syntax, and some compiler wizardry in the [Angular CLI](https://cli.angular.io/)…

Luckily, web standards have evolved since this syntax was introduced, and there’s now a “better” way to split our app and load each parts on demand!

{% gist https://gist.github.com/phenomnomnominal/2bb850d237283b67883c7d303f6d8c24 %}

## Want it right now? Cool! 🎉

If you’re already playing with Ivy, you can install a TSLint rule with a fixer to upgrading this automatically:

    npm install @phenomnomnominal/angular-lazy-routes-fix -D

Add the following to your `tslint.json`:

    {
        “extends”: [
            “@phenomnomnominal/angular-lazy-routes-fix”
        ],
        “//”: “either”,
        “no-lazy-module-paths”: [true],
        “//”: “or”,
        “no-lazy-module-paths”: [true, “async”]
    }

And then run:

    ng lint --fix

Voilà! All your lazy-loaded routes should be upgraded! 🎉

> ### ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ️️The upgraded code will only work with if the Ivy renderer is enabled, or if your app is running in JIT mode.

## What stopped us from doing this up until now?

This next section goes pretty deep into how lazy-loading works in Angular right now, and how it’s going to work in Angular in the future! Most of this is going away, but it’s still pretty interesting!

## Lazy-loaded routes in Angular 7.x.x

We use **`RouterModule.forChild()`** and **`RouterModule.forRoot()`** to tell Angular about the route structure of our application. But how does it work? Let’s check out the Angular source and find out!

If we dig into the implementation of **`RouterModule.forChild()`** and **`RouterModule.forRoot()`**, we can see that when we pass in the array of routes, they are registered as a [multi provider](https://angular.io/guide/dependency-injection-providers#predefined-tokens-and-multiple-providers) against the **`ROUTES`** **`InjectionToken`**:

{% gist https://gist.github.com/phenomnomnominal/4e2cad6a086b306ed5da6b8a489613bb %}

This means that at runtime we’re going to have an injectable array of route configuration objects! But how does Angular use these **`ROUTES`**? Again, let’s check out the Angular (7.x.x) source:

{% gist https://gist.github.com/phenomnomnominal/1533e03f8d9dd317f7f2a11ab77bfbb1 %}

The **`ROUTES`** are injected into the application's **`Router`** when it is created. When Angular encounters a route with a **`loadChildren`** property on it, it uses the **`RouterConfigLoader`** to try and figure out how to do that loading. We can see that the **`RouterConfigLoader`** does something differently based on if **`typeof loadChildren`** is a **`string`** or not… but doesn’t **`loadChildren`** *have* to be a string?

Let’s have a look at the declaration of the **`LoadChildren`** type:

{% gist https://gist.github.com/phenomnomnominal/9eda5a1e71587c582b3d3f6657cb0d8c %}

Isn’t that interesting! Even in a pre-Ivy world, **`loadChildren`** can be a **`string`** or an **`async function`**! So that should mean that our fancy **`import()`** syntax will already work? Let’s try it out:

{% stackblitz angular-1e8vwk %}

**What?** It does work! But how does this work?! Why have we been using the magic string syntax all along?!?!

The answer is **there’s a catch…** 🎣

### Lazy-loaded routes in Angular 7.x.x with Ahead of Time compilation

If we were to take our above application and build it with the prod flag (`ng build --prod`), everything appears to work! But when we try to navigate to our lazy-loaded route, we get a big red error:

![When running our app in AOT mode, we get an error: Runtime compiler is not loaded](https://thepracticaldev.s3.amazonaws.com/i/5rb85fycpujjopyqry0d.png)

This error makes sense! We used the `--prod` flag to enable the “Ahead-of-time” (**AOT**) compiler, which means we opted out of the “Just-in-time” (**JIT**) runtime compiler. If we look at where the error comes from, we can see it’s caused by the call to **`compileModuleAsync()`** in the **`RouterConfigLoader`**:

{% gist https://gist.github.com/phenomnomnominal/1aa75f8260dce51c267259041d336dfc %}

We end up down that **`else`** path because the **`instanceof`** check fails! When we use the **`import()`** operator with AOT, the object that we import from the lazy-loaded module is an **`NgModule`** instead of a **`NgModuleFactory`**. So how do we make sure that we are loading an **`NgModuleFactory`**?

### From `NgModule` to `NgModuleFactory` with the AOT Compiler:

The Angular compiler’s job is to statically analyse all of the code in our entire application, and to efficiently compile all of our templates and styles. It takes our **`NgModule`** files, and turns them into **`NgModuleFactory`** files, which contain the generated code that will create our views at runtime.

The compiler is able to start at a given file, and navigate through all of the import statements (e.g. `import { Thing } from './path/to/thing';` ) and build up a tree of all of the referenced modules. In order to split our application into chunks, we have to change our code to explicitly break this tree of references apart, while also making sure that the compiler knows about all the split parts of our application. The way we do this in an Angular application is with the **`loadChildren`** property, specifically with the magic string format:

{% gist https://gist.github.com/phenomnomnominal/d37142697a8b91bc5d8fdba536877bfa %}

The Angular AOT compiler finds all the **`ROUTES`** by using the **`InjectionToken`** and then looks for any strings using the **`./path/to/my.module#MyModule`** format. Each time it finds one, the compiler will start from the given path, build up the tree of referenced files, and compile each **`NgModule`** into an **`NgModuleFactory`**. If we don’t use that format, we don’t end up with the **`NgModuleFactory`** that the runtime needs. If we do use that format, then we end up with a generated file with an unknown path containing the **`NgModuleFactory``*, which means we can’t reference it with **`import()`**…

Altogether, this means that even though the types in Angular 7.x.x allow us to specify an async function for **`loadChildren`** it will never work in a production build of our application 😭😭😭. But why does the **`import()`** operator work in JIT mode?

The [**`import()`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports) operator is another way to declare that we want to lazily reference another part of our application. Modern tooling can detect it, mark the referenced path as another entry point, and lazily load the reference at runtime. Unfortunately, only the Angular CLI knows how to turn a **`NgModule`** into an **`NgModuleFactory`**, and it doesn’t know about **`import()`**. We saw it working because JIT mode only needs an uncompiled NgModule.

This is where we hit a bit of a dead end in Angular 7.x.x. For us to be able to use **`import()`**, something needs to change with how Angular works. Luckily for us, that change is just around the corner!

___

> You can learn more about how the Angular compiler works in [this incredible article](https://blog.angularindepth.com/a-deep-deep-deep-deep-deep-dive-into-the-angular-compiler-5379171ffb7a) by [Uri Shaked](https://dev.to/urishaked) ❤️:

___

## Lazy-loaded routes in Angular 8.x.x with Ivy:

One of the main design goals of the new Ivy renderer is to remove the differences between the JIT and AOT modes based on the principle of locality. Each file knows about everything that it needs to know about, without extra metadata files — this means no more **`NgModuleFactory`** classes!

That means that we no longer need to run a separate AOT compile, no longer have to worry about generated files with unknown paths, and we can use our **`import()`** operator!

___

> You can learn more about the changes in Ivy in [this great post](https://blog.angularindepth.com/ivy-engine-in-angular-first-in-depth-look-at-compilation-runtime-and-change-detection-876751edd9fd) by [Max Koretskyi](https://dev.to/maxim_koretskyi) ❤️:

___

## Upgrading from magic strings to nice async functions:

Now we know we have a cool new tool that we will be able to use soon! But we also have a lot of existing code that uses the magic string syntax. Wouldn’t it be great if there was an automatic way to upgrade all of our old code?

We can write a [custom TSLint rule and fixer](https://medium.com/@phenomnominal/custom-typescript-lint-rules-with-tsquery-and-tslint-144184b6ff2d) to do all this for us! Let’s look at the whole rule first, and then break it down:

{% gist https://gist.github.com/phenomnomnominal/76617fe08a20e75074e600411b6f7926 %}

First things first, we have a [TSQuery](https://medium.com/@phenomnominal/easier-typescript-tooling-with-tsquery-d74f04f2b29d) selector to choose the part of the code we want to modify:

    PropertyAssignment
    :not(:has(Identifier[name="children"]))
    :has(Identifier[name="loadChildren"])
    :has(StringLiteral[value=/.*#*/])

We use this selector in our rule to give us access to the right parts of our code:

{% gist https://gist.github.com/phenomnomnominal/7cc667d28504372580255df461d81104 %}

We can parse out the magic string, and create the replacement code. The fixer can generate code that uses either a raw `Promise` or `async/await`:

{% gist https://gist.github.com/phenomnomnominal/3ee7e8aa7a4985879f7a400779293d11 %}

Finally, we need to (somewhat clumsily) handle any indentation in the source code, and apply our fix:

{% gist https://gist.github.com/phenomnomnominal/2cc7bd06fffe07da3a1c193da128895c %}

And there we have it! One nice new sparkly TSLint fixer. If anyone wants to show me/help me how to turn this into an ESLint rule, then that’d be awesome 😇.

### The End!

Phew! How’s that for a brain dump of soon to be obsolete knowledge! I hope you learned a thing or two, maybe feel a little bit less scared about reading Angular source code, and maybe feel a bit inspired to write your own automation for upgrading your apps. Please reach out to me with any questions, and I’d love your feedback!

❤️ 🦄

P.S. Big thanks to [Thomas Burleson](https://dev.to/thomasburleson) for “encouraging” me to write stuff down!