Back to Blog Home
← all posts

Lazy Loading in NativeScript Angular 8.0

September 24, 2019 — by Stanimira Vlaeva

Angular 8.0 deprecates the string syntax for configuring lazy loaded routes in favor of the EcmaScript dynamic import. The import(specifier) syntactic form returns a Promise for the module namespace object. After the Promise is resolved, we fetch the exported LazyModule.

Before

const routes: Routes = [{
  path: 'lazy',
  // The following string syntax for loadChildren is deprecated
  loadChildren: './lazy/lazy.module#LazyModule'
}];

After

const routes: Routes = [{
  path: 'lazy',
  // The new import() syntax
  loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
}];

NativeScript Angular Support

You can use the new lazy loading syntax in NativeScript Angular as well!

The dynamic import proposal is scheduled for ES2020. It has also been implemented by all major browsers. Since it's quite new, you need to tell TypeScript to use the latest EcmaScript module spec when compiling your code. Open your tsconfig.tns.json file and set the module property to esNext:

tsconfig.tns.json

{
    "extends": "./tsconfig",
    "compilerOptions": {
        "module": "esNext",
        "moduleResolution": "node"
    }
}
Important! The NativeScript Playground doesn't support the dynamic imports yet. For now, stick to the old lazy loading syntax when writing your NativeScript Angular applications in the Playground.

Automatic Route Migration

Migrating all your routes to the new syntax can be quite tedious. But you can tell TSLint to do it for you!

First, make sure you have TSLint in your project:

npm install tslint --save-dev

After that, install the angular-lazy-routes-fix TSLint rule, created by Craig Spence:

npm install @phenomnomnominal/angular-lazy-routes-fix --save-dev

Then, add the rule to your TSLint configuration. If you don't have one, just create a new tslint.json file:

tslint.json

{
  "extends": [
    "@phenomnomnominal/angular-lazy-routes-fix"
  ],
  "rules": {
    "no-lazy-module-paths": [true]
  }
}

Finally, run TSLint with the --fix flag:

npx tslint -p tsconfig.json --fix

The linter will find all usages of the old lazy loading syntax and replace them with dynamic imports.

How does it work?

The NativeScript runtimes don't support the import() syntax natively. However, with some help from webpack and Angular, you can use it in your routing configurations. When you build the project with webpack, the following code:

const routes: Routes = [{
  path: 'lazy',
  loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
}];

is roughly converted to:

const routes: Routes = [{
  path: 'lazy',
  loadChildren: requireEnsure('./1.js').LazyModule
}];

The file 1.js is the lazily-loaded bundle for the /lazy route. It contains the NgModule, called LazyModule together with all Components, Directives and other NgModules that it depends on.

Sidenote: 1.js don't contain any third-party packages. Instead, all packages from node_modules required somewhere in your application end up in a bundle, called vendor.js.

The lazily-loaded bundle is loaded from the file system by the requireEnsure function. This function is inserted by webpack. The requireEnsure function has a different definition if you are building for the browser, Node, or NativeScript.

Sidenote: In NativeScript, the definition of the function is generated from a template that lives in the nativescript-target for webpack.

For NativeScript, the implementation of the requireEnsure functions simply loads the file with a require() call, and then - caches it. So, in the end, you never actually ship a dynamic import() in your application - it's all converted to a good old CommonJS require(). The NativeScript runtimes implement the require() function by loading the file from the device file system.

Summary

The key takeaways from the whole module loading story are:

  • Angular 8.0 now recommends loading lazy modules with dynamic import().
  • NativeScript Angular 8.0 supports the new configuration syntax, although you still can't use it in the NativeScript Playground.
  • You can use TSLint to migrate your existing route configurations.
  • Dynamic import() is not supported natively but webpack replaces it with require() during build.