Lazy loading of Feature Module from the «node_modules» folder

If someone of you has tried create angular libraries, he may face the issue with lazy loading Feature Module from node_modules. Let's dive deeper and go thru the dark water.


Creating library tools


image


If you are not familiar with question "How to create the library?" — There are at least two tools for creating angular library:


  1. Angular CLI (ng-packagr under the hood);
  2. Directly the ng-packagr;

You able to get more details by clicking on the links above, all other let's move further.


Lazy Modules (Feature Modules)


image
So, If you have already worked with Angular you have to know about Angular modules and for what purposes you need them.


A module usually has to keep a bunch of components, services of directives which represent a certain self-sufficient piece of functionality. Often developer creates a separate module for a feature which has not to be loaded to app until a user will not go to particular page.


And now the time for Feature Store (not about this for now) and Feature module.


If you include Lazy Loaded modules into some parent module they wouldn't be lazy, because you will declare them into the app and in time of compilation TypeScript into the JavaScript Webpack will add the code of those modules into the main bunch and application will load all that code on startup.


Only one correct way to declare a lazy module in routing.


const routes: Routes = [
  {
    path: 'reports',
    loadChildren: './reports/reports.module#ReportsModule'
  }
];

So you provide the special string which actually "#" and the app will know that this module has to be loaded separately from the main app.

Troubles Begin


image
All goes fine if Feature Module is a part of source code of your app, but what if this is a compiled library set up using the npm? — It does not work in a usual way.


The main problem that when you try to register such Feature Module by usual string you will facing the error.


const routes: Routes = [
  {
    path: 'reports',
    // will not work
    loadChildren: 'my-lib#ReportsModule'
  }
];

It causes due to such kind of declaration are not compatible for compiled modules.


You able to make a wrapper and then declare it in routs.


import { ReportsModule } from "my-lib";
@NgModule({
  imports: [ReportModule],
  exports: [ReportModule]
})
export class ReportsWrapperModule { }
const routes: Routes = [
  {
    path: 'reports',
    loadChildren: './wrapper.module#ReportsWrapperModule'
  }
];

BUT, who interested in creating 15 wrappers for 15 modules? — I'm not!


Possible solutions


image


I won’t hide I didn't waste time analyzing why this happens due to Webpack or some Angular CLI. I found two possible solutions.


Solution #1. Library compiled and Feature Module has no nested feature modules


Link for the working example.


First step define what type of LoadChildren attribute


type LoadChildren = string | LoadChildrenCallback;
type LoadChildrenCallback = () => Type | NgModuleFactory | Promise> | Observable>;

Interesting! So, theoretically, it's possible to use import() to return promise with the required module.
Next code has to be fine.


const routes: Routes = [
  {
    path: 'reports',
    loadChildren: () => import('my-lib').then((res) => res.ReportsModule)
  }
];

Oh, what happens? — We see errors in the console… Yep, just change tsconfig.json


{
  ..
  "module": "esNext",
  ..
}

Now it has to work fine.


Solution #2. Feature Module have nested feature modules (done only for a non-compiled library)


Link for the working example.


  1. You have not to compile the library before packaging to npm package;


  2. As the source of library is not compiled you have to include it into compilation process of your app. Just change tsconfig.app.json;


    "exclude": [
      "test.ts",
      "**/*.spec.ts",
      "../node_modules/**/*.spec.ts",
      "../node_modules/**/test.ts"
    ],
    "include": [
      "*.ts",
      "./environments",
      "./app",
      "../node_modules/my-lib"
    ]

  3. Good news — no need to use import();


    const routes: Routes = [{
    path: "reports",
    loadChildren: "my-lib#ReportsModule"
    }];

    Tadam!



P.S. These solutions tested with Angular 7 only.


Thanks for attention, enjoy.


Also popular now: