Angular-cli: Lazy loading module from library package located in node_modules fails to build.

Created on 18 May 2017  ·  80Comments  ·  Source: angular/angular-cli

Bug Report or Feature Request (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request

Versions.

@angular/cli: 1.0.4
node: 6.10.2
os: win32 x64
@angular/cli: 1.0.4
@angular/common: 4.1.3
@angular/compiler: 4.1.3
@angular/core: 4.1.3
@angular/forms: 4.1.3
@angular/http: 4.1.3
@angular/platform-browser: 4.1.3
@angular/platform-browser-dynamic: 4.1.3
@angular/router: 4.1.3
@angular/compiler-cli: 4.1.3

Repro steps.

I've modified the quick start library from @filipesilva and copied the output folder to my node_modules/quickstart-lib directory.

https://github.com/filipesilva/angular-quickstart-lib

Modification includes a routing module to add a child route.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LibComponent } from './component/lib.component';
const childRoutes: Routes = [
  {
    path: '',
    component: LibComponent
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(childRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class LibRoutingModule { }

which is then imported into the main LibModule file.

import { NgModule } from '@angular/core';
import { LibRoutingModule } from './lib-routing.module';
import { LibComponent } from './component/lib.component';
import { LibService } from './service/lib.service';

@NgModule({
  declarations: [LibComponent],
  imports: [LibRoutingModule],
  providers: [LibService],
  exports: [LibComponent]
})
export class LibModule { }

In my host application, I created a simple route to lazy load the module from the library

const routes: Routes = [
  {
    path: 'quickstart',
    loadChildren: 'quickstart-lib#LibModule'
  },
  {
    path: 'preferences',
    loadChildren: 'app/preferences#PreferencesModule'
  },
  ...HomeRoutes
];


The log given by the failure.

PS C:\Workspace\scratch\Ng4Starter\host> ng serve
** NG Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200 **
 11% building modules 14/23 modules 9 active ...gular\platform-browser-dynamic.es5.js(node:19124) DeprecationWarning: loaderUtils.parseQuery() received a non-string value which can be problematic,
 see https://github.com/webpack/loader-utils/issues/56
parseQuery() will be replaced with getOptions() in the next major version of loader-utils.
 11% building modules 15/23 modules 8 active ...gular\platform-browser-dynamic.es5.js(node:19124) DeprecationWarning: loaderUtils.parseQuery() received a non-string value which can be problematic,
 see https://github.com/webpack/loader-utils/issues/56
parseQuery() will be replaced with getOptions() in the next major version of loader-utils.
Hash: c46342c7aafb5ce0f53e
Time: 14214ms
chunk    {0} 0.chunk.js, 0.chunk.js.map 20.5 kB {3} [rendered]
chunk    {1} 1.chunk.js, 1.chunk.js.map 793 bytes {0} {3} [rendered]
chunk    {2} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 157 kB {7} [initial] [rendered]
chunk    {3} main.bundle.js, main.bundle.js.map (main) 72.5 kB {6} [initial] [rendered]
chunk    {4} styles.bundle.js, styles.bundle.js.map (styles) 229 kB {7} [initial] [rendered]
chunk    {5} scripts.bundle.js, scripts.bundle.js.map (scripts) 162 kB {7} [initial] [rendered]
chunk    {6} vendor.bundle.js, vendor.bundle.js.map (vendor) 3.36 MB [initial] [rendered]
chunk    {7} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]

ERROR in ./~/quickstart-lib/quickstart-lib.d.ts
Module build failed: Error: Debug Failure. False expression: Output generation failed:=>C:/Workspace/scratch/Ng4Starter/host/node_modules/quickstart-lib/quickstart-lib.d.ts
    at Object.assert (C:\Workspace\scratch\Ng4Starter\host\node_modules\typescript\lib\typescript.js:3259:23)
    at Object.transpileModule (C:\Workspace\scratch\Ng4Starter\host\node_modules\typescript\lib\typescript.js:76181:18)
    at TypeScriptFileRefactor.transpile (C:\Workspace\scratch\Ng4Starter\host\node_modules\@ngtools\webpack\src\refactor.js:161:27)
    at Promise.resolve.then.then.then.then (C:\Workspace\scratch\Ng4Starter\host\node_modules\@ngtools\webpack\src\loader.js:361:37)
    at process._tickCallback (internal/process/next_tick.js:109:7)
 @ ./src async
 @ ./~/@angular/core/@angular/core.es5.js
 @ ./src/main.ts
 @ multi webpack-dev-server/client?http://localhost:4200 ./src/main.ts
webpack: Failed to compile.

Desired functionality.


I would like to distribute my code as an NPM library and have the CLI support lazy loading the module that is located in the 'node_modules' directory.

Modules located in the same application directory are lazy loaded just fine.

Mention any other details that might be useful.

https://github.com/filipesilva/angular-quickstart-lib

devkibuild-angular faq

Most helpful comment

I've got work around for this which works with uirouter/angular-hybrid (in AOT mode too) but might work with angular router too.

So, let's say I've got a library based on angular-quickstart-lib (let's call it quickstart-lib) which has LibModule that I want to lazy load. To do so, I added wrapper around library which lives in Angular CLI based host project.

So, in app.module.ts of host, instead of:

@NgModule({
  imports: [
    UIRouterModule.forChild({
    states: [
        {
          name: 'quickstart.**',
          url: 'quickstart',
          loadChildren: 'quickstart-lib#LibModule'
        }
    ]}),
  ]
})
export class AppModule

I've got:

libModuleLazyLoader.ts

import { NgModule } from "@angular/core";
import { LibModule } from 'quickstart-lib';

@NgModule({
  imports: [
      LibModule
  ]
})
export class QuickstartLibLazyLoader {}

and then, in app.module.ts, I've got:

@NgModule({
  imports: [
    UIRouterModule.forChild({
    states: [
        {
          name: 'quickstart.**',
          url: 'quickstart',
          loadChildren: './libModuleLazyLoader#QuickstartLibLazyLoader'
        }
    ]}),
  ]
})
export class AppModule

Downside is that you'd need wrapper per library with this work around.

hope this helps someone :)

All 80 comments

I'm having the same issue with version:

@angular/cli: 1.0.4 
@angular: 4.0.0

Hopefully this can be fixed soon, I'll continue by using --no-aot for the moment.

@mdewaelebe doesn't the CLI default to JIT? This error happens for me even if I use --aot false. I don't see a --no-aot option.

@thekhoi I tried the approach described in #5153.
It seems you're right, the --no-aot option is not found anywhere, but it does the trick for me.

ng build --prod : fails
ng build --prod --no-aot : succeeds

I did no further investigation on why or how this works.

@mdewaelebe I think that is a different issue. I get the same error for a lazy route that happens to be local to my project directory or not.

ERROR in ./src/$$_gendir async
Module not found: Error: Can't resolve 'C:\Workspace\scratch\Ng4Starter\host\src\$$_gendir\app\foo\index.ngfactory.ts' in 'C:\Workspace\scratch\Ng4Starter\host\src\$$_gendir'
 @ ./src/$$_gendir async
 @ ./~/@angular/core/@angular/core.es5.js
 @ ./src/main.ts
 @ multi webpack-dev-server/client?http://localhost:4200 ./src/main.ts
webpack: Failed to compile.

I was able to track down the issue. It appears that the AoT plugin discovers lazy routes and passes them to the angular compiler language service to resolve the file name of a given route string. Assuming your library is following the angular package format, this resolves to a definition file, e.g. c:...\node_modules\lib-name\lib-name.d.ts.

The file @ngtools\webpack\src\loader.js then uses this path to transpile the source code to es2015 which fails. I modified this file to check for the *.d.ts extension. If there is a match I just use the contents of the pre-compiled .js and .map files for the library. This lets me get past the compilation error in the CLI and when watching the network traffic, I can see the module is loaded on demand.

However, this is another hickup, the library is already bundled in the vendor.js file since the webpack configuration simply bundles everything in the node_modules directory into a the vendors file.

The only way to get around this, is to eject out of the CLI to exclude the library from the common vendor chunk.

All these fixes seem to be a hack, hopefully the cli team can come up with something more elegant.

https://github.com/angular/angular-cli/issues/6755 contains a nice repro of this issue.

Hello @filipesilva, I have the same issue. Is there any news about a workaround?

Thanks,

ERROR in ./~/@project/myLib/lib/index.d.ts
Module build failed: Error: Debug Failure. False expression: Output generation failed
    at Object.assert (<path>\node_modules\typescript\lib\typescript.js:3329:23)
    at Object.transpileModule (<path>\node_modules\typescript\lib\typescript.js:80449:18)
    at TypeScriptFileRefactor.transpile (<path>\node_modules\@ngtools\webpack\src\refactor.js:161:27)
    at Promise.resolve.then.then.then.then (<path>\node_modules\@ngtools\webpack\src\loader.js:378:37)
    at process._tickCallback (internal/process/next_tick.js:103:7)
 @ ./src async
 @ ./~/@angular/core/@angular/core.es5.js
 @ ./src/main.ts
 @ multi ./src/main.ts

@angular/[email protected]
typescript@~2.3.3

const routes: Routes = [
    {
        path: 'myRoute', component: MyComponent,
        children: [
            { path: "user", loadChildren: "@project/myLib#MyModule" }
        ]
    }
];

I've got work around for this which works with uirouter/angular-hybrid (in AOT mode too) but might work with angular router too.

So, let's say I've got a library based on angular-quickstart-lib (let's call it quickstart-lib) which has LibModule that I want to lazy load. To do so, I added wrapper around library which lives in Angular CLI based host project.

So, in app.module.ts of host, instead of:

@NgModule({
  imports: [
    UIRouterModule.forChild({
    states: [
        {
          name: 'quickstart.**',
          url: 'quickstart',
          loadChildren: 'quickstart-lib#LibModule'
        }
    ]}),
  ]
})
export class AppModule

I've got:

libModuleLazyLoader.ts

import { NgModule } from "@angular/core";
import { LibModule } from 'quickstart-lib';

@NgModule({
  imports: [
      LibModule
  ]
})
export class QuickstartLibLazyLoader {}

and then, in app.module.ts, I've got:

@NgModule({
  imports: [
    UIRouterModule.forChild({
    states: [
        {
          name: 'quickstart.**',
          url: 'quickstart',
          loadChildren: './libModuleLazyLoader#QuickstartLibLazyLoader'
        }
    ]}),
  ]
})
export class AppModule

Downside is that you'd need wrapper per library with this work around.

hope this helps someone :)

@adamlubek it works! Thanks!

Using a different router isn't helpful and I didn't think the CLI supported anything other than the default router. What is the solution to this problem?

@babeal yes, instead of default Angular router you can use https://github.com/ui-router/angular for pure Angular project or https://github.com/ui-router/angular-hybrid for hybrid AngularJs/Angular project

That said, it looks like @Chabane is using default router and got it working. @Chabane , is that correct?

@adamlubek yes perfect!

The key to work around from @adamlubek is the 'local' module, QuickstartLibLazyLoader, imports the installed 'LibModule'.

This mostly works however it breaks if you try to do a production build.

ng build --prod

Turning off AOT will suppress the issue

ng build --prod --aot false

@thekhoi , that's strange as my workaround works for me in production build with AOT. Does your library work with AOT when it's not lazy loaded using my work around?

Maybe you have non AOT compliant code somewhere in your library?

@adamlubek , you're right. It looks like there was an issue with the node package I created for testing. My package name was scoped @foo/bar but the "importAs" attribute in my bar.metadata.json file was set to "bar".

I have the same issue.
@adamlubek s workaround worked for me.
Is this issue going to get resolved?

Is it going to be fixed?

I had similar problem. Solution can be found in this stackoverflow answer:

https://stackoverflow.com/questions/46774625/aot-compiler-include-lazy-loaded-external-module

And in my Angular boilerplate repository:

https://github.com/maciejtreder/angular-universal-pwa

Is this issue going to be fixed?

@maciejtreder

your solution https://github.com/maciejtreder/angular-universal-serverless
doesn't use angular-cli

I tried your workaround with angular-cli.It didn't work.
I got the same error "Module build failed: Error: Debug Failure. False expression: Output generation failed"

Can you tell us how can we make it work with angular-cli

Any news?

Any new guys?

@adamlubek For my situation the workaround is not working. I have a local module which imports another module from the node_modules folder. The chunking works however my chunk only contains the import. The code of the imported module ends up in the vendor.bundle.js.

Am I doing something wrong?

@appienvanveen The modules show up in the vendor bundle if it's referenced in more than one place. Make sure your imported module isn't referenced outside elsewhere.

-Khoi

@thekhoi Thanks for your reply. When I remove the routing/loading of the lazy module, the module does not get loaded in de vendor.bundle.js. This would mean it isn't referenced elsewhere, does it?

@appienvanveen sorry but I don't follow your example.

Generally though, in order for lazy loading to work properly, you can't have code (that you want lazy loaded) referenced anywhere in your code base through import statement (as it would 'tell' cli to eagerly load the code). You have to have your module referenced ONLY using config object passed into router module setup. So, in my earlier comment here (https://github.com/angular/angular-cli/issues/6373#issuecomment-319116889), the crucial bit is:

loadChildren: './libModuleLazyLoader#QuickstartLibLazyLoader'

@adamlubek Thanks for replying. I'm pretty sure my module isn't referenced elsewhere. Hopefully the code below will clear things up!

_app-routing.module.ts_ <-- My application routing

import {Route, RouterModule} from '@angular/router';

import {ModuleWithProviders} from '@angular/core';
import {HomeComponent} from './home/home.component';

export const APP_ROUTES: Route[] = [
  { path: 'pas', loadChildren: './pas-lazy.module#PasLazyModule' },
  {
    path: '',
    component: HomeComponent
  }
];

_pas-lazy.module.ts_ <-- This is my 'wrapper' module; this module isn't referenced elsewhere.

import { NgModule } from '@angular/core';
import {PasModule} from '@xyz/pas';

@NgModule({
  imports: [
    PasModule   ///<-- this module is imported from node_modules which I want to be lazy loaded
  ],
  declarations: []
})
export class PasLazyModule { }

@appienvanveen how do you pass your APP_ROUTES to RouterModule? Are you using:

RouterModule.forChild(APP_ROUTES);

?

Ah, I noticed your using UIroutermodule, which I do not. It looks like it does not work without UIroutermodule.

@Chabane here confirmed that he got it working with default angular router too (see https://github.com/angular/angular-cli/issues/6373#issuecomment-322430684)

also just to double check, are you using RouterModule.forChild(APP_ROUTES); ?

In my main app.module i'm using RouterModule.forRoot(APP_ROUTES),

If I change this in forChildI get the error: No provider for ChildrenOutletContexts!

I think this is where your problem is. Official documentation and other resources (e.g. rangle.io) recommend using forChild for all feature modules:

https://angular.io/guide/lazy-loading-ngmodules#forroot-and-forchild

The CLI also adds RouterModule.forChild(routes) to feature routing modules. This way, Angular knows that the route list is only responsible for providing additional routes and is intended for feature modules.

https://angular-2-training-book.rangle.io/handout/modules/lazy-loading-module.html

Notice that we use the method call forChild instead of forRoot to create the routing object. We should always do that when creating a routing object for a feature module, no matter if the module is supposed to be eagerly or lazily loaded.

So, I'd try using forChild and figure out how to solve your No provider for ChildrenOutletContexts! error

@Chabane here confirmed that he got it working with default angular router too (see #6373 (comment))
also just to double check, are you using RouterModule.forChild(APP_ROUTES); ?

@adamlubek .. I can confirm that this is working by creating a wrapper module and with RouterModule.forChild

But whats the thought process towards accepting a direct link from node_modules in loadChildren? I feel this seems to be more friendly..

@hansl Can #7659 be merged? This looks like the missing piece to fix this issue.

Can confirm that @adamlubek's workaround works. I can also confirm that the loaded package appears both in the vendor chunk and the lazy loaded chunk as mentioned by @thekhoi.

We are still facing the issue, using another router-lib is not a solution. Any traction on this issue??

@adamlubek's solution is pretty elegant but,... as @Web2Integrators said, not use angular-cli;
MY situation is more or less the same as @appienvanveen ; (application with an external feature module that via loadchildren try to load an external feature module);

tsc compiler with SystemJs builer is, ...so far, mine state of art;

waiting for do the same even with angular-cli;

Is there any solution for this yet from angular-cli?

Hello, I believe Angular applications are going to be bigger and bigger. So this issue becomes more and more important. Is there any solution for this yet from angular-cli?

Facing the same issue routing to external UMD module which is inside 'node_modules'

It throws below error while building application in --prod mode

Error
typeerror

Please help us to solve the issue
Thanks

The interesting thing here is that I have different error:

With routing like:

const routes: Routes = [
  {
    path: 'libContent',
    loadChildren: 'my-lib#MyLibModule'
  }
];
...
  imports: [
    RouterModule.forRoot(routes),
...

it throws error like this:

my-lib\my-lib.d.ts is missing from the TypeScript compilation. Please make sure it is in your tsconfig via the 'files' or 'include' property.

but it works if I pre-build it in the main bundle like so:

import { MyLibModule } from 'my-lib';

const routes: Routes = [
  {
    path: 'libContent',
    loadChildren: () => MyLibModule 
  }
];
...

It works just like expected

@Nodarii It is @angular/compiler-cli that delegates to Typescript to resolve the my-lib module, and this assumes that there is a my-lib.ts file at node_modules/my-lib/my-lib.ts for us using ng-packagr, but instead there is only the typings. That's the error you are seeing, it is a red herring. It seems that @angular/compiler-cli should support resolving modules that support the Angular Package Format, and it currently does not.

@Nodarii @vanslly I have same issue with angular cli v6.0.3. Did you guys solved that? or created issue?

@batdelger, for now I do not use lazy loading, just load moule like so loadChildren: () => MyLibModule and will replace it when this issue will be fixed

I got lazy loading working with cli, standard angular router (not ui), lazy loading and... a couple of workarounds.

Please find a working example here: https://github.com/danielecanteri/angular-lazy-loading-modules.

Basically I had to create a wrapper module (as suggested in comments above).
In the wrapper module I had to re-register the child routes (exported from the module).

import {ExternalModuleLibModule, routes} from 'external-module-lib';
...
@NgModule({
  imports: [
    CommonModule,
    ExternalModuleLibModule,
    // hack: re-register routes for imported module
    RouterModule.forChild(routes)
  ],
  declarations: []
})
export class ExternalModuleWrapperModule { }

After that I can use lazy loading routes int the application as follows:

{
  path: 'external-module',
  loadChildren: './external-module-wrapper/external-module-wrapper.module#ExternalModuleWrapperModule'
},

@danielecanteri I have tried your suggestion and I see it is working fine with 1 external lib module.
I have multiple external lib modules and when I try what you have suggested I always get routed to the first imported module from the imports section.

Do you know if I am supposed to have a lazy wrapper for each module?
From the other comments I have understand that only 1 wrapper is needed per external library but I have not managed to figure that part out.

import { ExternalModule1, ExternalModule2 } from "@namespace/external-lib";

@NgModule({
    imports: [
        ExternalModule1,
        ExternalModule2
    ]
  })
  export class LazyWrapperModule {}

I see this as a seriously problematic issue for enterprise projects.
Currently one of few issues keeping me away from using angular-cli.

Is there any chance at all for this issue to get resolved?

I just decided to publish my lazy modules without bundling ( to our local
npm registry). Actually it is not very bad thing. Building them at once is
more safe. To do that you must add your lazy libraries to tsconfig.app.json

On Да, 2018 6-р сар 11 05:08 Klemen Oslaj notifications@github.com wrote:

I see this as a seriously problematic issue for enterprise projects.
Currently one of few issues keeping me away from using angular-cli.

Is there any chance at all this issue will get resolved?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular-cli/issues/6373#issuecomment-396082110,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AC70Cu6qeRA8EyG0Xv8fjcTg3cBMcafVks5t7YrigaJpZM4Nfpev
.

I have created a Customer App as per https://angular.io/guide/lazy-loading-ngmodules; I also integrated a lib Angular 6 workspace lib module named inventory-lib; in my app-routing.module.ts file I implemented routing

const routes: Routes = [
{
path: 'customers',
loadChildren: 'app/customers/customers.module#CustomersModule'
},
{
path: 'orders',
loadChildren: 'inventory-lib/orders.module#OrdersModule'
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];

Note: inventory-lib is not a actual folder but it is a ng6 workspace lib https://blog.angularindepth.com/creating-a-library-in-angular-6-87799552e7e5

Not the order route is not loading or no error in console

GITHUB repo to replicate error https://github.com/akhilshastri/lazy-loading-with-ng6-lib-module

@batdelger yes I am doing the same now...
Publishing modules without bundling, or using Angular CLI 6 libraries, but again I do not bundle them.
This of course works, but it is still just a workaround.

Seems to be the same problem I created an issue for https://github.com/angular/angular/issues/24519.

Why hasn't this issue been a high priority for a fix? Are we not supposed to lazy load modules from Angular 6 libraries?

Don't know why angular guys have become so irresponsive, the issue is
causing us problems.

On Sun, Jul 22, 2018, 7:06 AM Anthony Prado notifications@github.com
wrote:

Why hasn't this issue been a high priority for a fix? Are we not supposed
to lazy load modules from Angular 6 libraries?


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular-cli/issues/6373#issuecomment-406834793,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AcrHe3XaEUo-b0mkisndaGexq8uEJTIZks5uI9cYgaJpZM4Nfpev
.

Any updates on the how we could directly load from node modules rather creating an wrapper

For my current project it is necessary to reuse feature modules.
So i´m looking forward for a solution because usinge the provided workaround is a huge overhead if you have multiple modules.

Any update on this issue??

@filipesilva @Brocco any idea, if this issue is going to be considered or it would be great if you let us know if you would be fixing this issue anytime sooner

I understand there could be a reason, would you please give us Justification, why this is not considered, as this is open since a year back

Eagerly waiting for the response from angular cli team

I've tried the @Nodarii 's solution, but didn't worked:
loadChildren: () => MyLibModule
But it worked using a named function:
export function loadMyLibModule() { return MyLibModule; }
...
loadChildren: loadMyLibModule

I don't think this issue will be resolved in v6. We'll have to wait till at least 7 and possibly Ivy

A proxy module works for now, but looking forward to a solution in 7.0.

@tiagoa Are you sure that this is real lazy loaded?
Since you use return MyLibModule, and in that way it needs to be imported. At the end, it is working, but it is not lazy loaded.

@mnicic yeah, @tiagoa solution is not lazy loading. If there's an import in your routing you will bring all the source together. Currently the only working solution is the proxy, and seeing the lack of interest from the development team on this issue, until someone develops another routing solution for angular, there won't be alternative.

@mnicic yeah, @tiagoa solution is not lazy loading. If there's an import in your routing you will bring all the source together. Currently the only working solution is the proxy, and seeing the lack of interest from the development team on this issue, until someone develops another routing solution for angular, there won't be alternative.

What's the problem with the working solution? The proxy module is lazy loaded by the router. Your only overhead is the proxy module around your actual module, that you wanted to lazy load.

@mnicic yeah, @tiagoa solution is not lazy loading. If there's an import in your routing you will bring all the source together. Currently the only working solution is the proxy, and seeing the lack of interest from the development team on this issue, until someone develops another routing solution for angular, there won't be alternative.

What's the problem with the _working_ solution? The proxy module is lazy loaded by the router. Your only overhead is the proxy module around your actual module, that you wanted to lazy load.

The problem with the working solution is that it's a hack to make it work around an issue, that it's not being able to lazy load modules installed through npm. This produces inconsistent source code on applications, where you have local modules without the need of a proxy module, and node modules with a proxy requirement. But things get worse, this issue, is not mentioned on official angular documentation, so if you try to do this at first time, you lose time trying to make work something that should work until you google it and find this issue growing with comments from people who find this issue in their projects over and over again. Try to keep the "modular" philosophy angular proposes and then publish your reusable routed modules into npm or your private node repository, and now you have to create a proxy for every "reusable" module that isn't reusable without it after all.

I was able to make it work use this function to load the module

lazyModuleLoad = function <T>(cl: Type<T>): any { return function (): Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>> { return new Promise<Type<any>>(resolve => resolve(cl)); }; };
And in path I call the function like that:

path: '', loadChildren: lazyModuleLoad(SomeModule)

I was able to make it work use this function to load the module

lazyModuleLoad = function <T>(cl: Type<T>): any { return function (): Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>> { return new Promise<Type<any>>(resolve => resolve(cl)); }; };
And in path I call the function like that:

path: '', loadChildren: lazyModuleLoad(SomeModule)

Your solution will compile the module at runtime and won´t load it lazy.
And it won´t work with AOT compile.

Hi,
I'm developing a dashboard like application. I am developing widgets in angular cli libraries, then compiling these libraries and publishing them to our private NPM registry. These libraries export a NgModule which declares its widgets and makes them entryComponents for dynamic rendering.
Then, in the host app, which installs these NPM packages, I am trying to dynamically load these modules, if requested via configuration, to get their widgets' component factories.
The problem I'm facing is that I can't seem to be able to lazy load these precompiled NgModules in any way. I've tried following many good guides I found in stackoverflow but those always talk about loading source files that export an NgModule, not something that is already built in AOT mode from an NPM package.
Reading this topic it seems it is not doable... and that I should publish NPM packages containing the source code instead of a compiled library. Is it the case or is there a workaround/official fix after 1 and a half years this topic was open?

Hi,
I'm developing a dashboard like application. I am developing widgets in angular cli libraries, then compiling these libraries and publishing them to our private NPM registry. These libraries export a NgModule which declares its widgets and makes them entryComponents for dynamic rendering.
Then, in the host app, which installs these NPM packages, I am trying to dynamically load these modules, if requested via configuration, to get their widgets' component factories.
The problem I'm facing is that I can't seem to be able to lazy load these precompiled NgModules in any way. I've tried following many good guides I found in stackoverflow but those always talk about loading source files that export an NgModule, not something that is already built in AOT mode from an NPM package.
Reading this topic it seems it is not doable... and that I should publish NPM packages containing the source code instead of a compiled library. Is it the case or is there a workaround/official fix after 1 and a half years this topic was open?

I already posted this in a similar issue (could be a duplicate, but not entirely sure).
https://github.com/angular/angular-cli/issues/12859#issuecomment-437582885

The loading from webpack is actually working fine, as in it found the correct modules to load via lazy loaded references, but the actual ngfactories for those modules have not been build in the process.

You can try my workaround. Basically what you want is to somehow force the compiler to build your factory, but don't have it bundled into the main chunk but into their respective separate chunks.

Hi,
I'm developing a dashboard like application. I am developing widgets in angular cli libraries, then compiling these libraries and publishing them to our private NPM registry. These libraries export a NgModule which declares its widgets and makes them entryComponents for dynamic rendering.
Then, in the host app, which installs these NPM packages, I am trying to dynamically load these modules, if requested via configuration, to get their widgets' component factories.
The problem I'm facing is that I can't seem to be able to lazy load these precompiled NgModules in any way. I've tried following many good guides I found in stackoverflow but those always talk about loading source files that export an NgModule, not something that is already built in AOT mode from an NPM package.
Reading this topic it seems it is not doable... and that I should publish NPM packages containing the source code instead of a compiled library. Is it the case or is there a workaround/official fix after 1 and a half years this topic was open?

I already posted this in a similar issue (could be a duplicate, but not entirely sure).
#12859 (comment)

The loading from webpack is actually working fine, as in it found the correct modules to load via lazy loaded references, but the actual ngfactories for those modules have not been build in the process.

You can try my workaround. Basically what you want is to somehow force the compiler to build your factory, but don't have it bundled into the main chunk but into their respective separate chunks.

I don't think the issue is the same.
I am trying to load modules from NPM packages without really knowing if these modules are even installed in the host app. This app, at startup, gets a configuration object from the server which tells which plugins should be enabled.
The app then tries to load these plugins via http request (the paths to the NgModules contained in the NPM packages come with the configuration and aren't written anywhere in the code, so webpack can't know them) and use the ngfactory to then extract its components.
All the examples I've seen just load the lib.module.ts#LibModule and then feed that file to the angular compiler. I want to download via HTTP the already compiled ngfactory (compiled by the external developer that publishes the NPM package with its widgets) and extract its components.

@angular-devkit/architect          0.10.7
@angular-devkit/build-angular      0.10.7
@angular-devkit/build-ng-packagr   0.10.7
@angular-devkit/build-optimizer    0.10.7
@angular-devkit/build-webpack      0.10.7
@angular-devkit/core               7.0.7
@angular-devkit/schematics         7.0.7
@angular/cli                       7.0.7
@ngtools/json-schema               1.1.0
@ngtools/webpack                   7.0.7
@schematics/angular                7.0.7
@schematics/update                 0.10.7
ng-packagr                         4.4.5
rxjs                               6.3.3
typescript                         3.1.6
webpack                            4.19.1

Both workarounds worked for me:
https://github.com/angular/angular-cli/issues/6373#issuecomment-386625097 << preferred
https://github.com/angular/angular-cli/issues/6373#issuecomment-319116889

So why is this not a priority? Lets say we have a bigger business application where all pages are located in a lib. For each customer we create a custom app out of the pages. When you have so many pages Lazy Loading is the way to go because the startup time would be too high otherwise. Now I have to create a wrapper module for 50 pages to make it work correctly?? That is not a reasonable solution at all...

I'm guessing because the Ivy renderer will fix this issue (and change the way to deal with these things anyway) and they don't want to spend time fixing the "old" behavior.

I'm guessing because the Ivy renderer will fix this issue (and change the way to deal with these things anyway) and they don't want to spend time fixing the "old" behavior.

Well, that is a valid and reasonable explanation, however, this issue and other related issues are hunting angular community since quite a while now, while having no official statement(_apart from Ivy recently_).

Sure, Ivy will fix that, but why didn't fix happened earlier(_without Ivy_) given the fact the issue really puts a stain on Angular itself IMHO.

@adamlubek , you're right. It looks like there was an issue with the node package I created for testing. My package name was scoped @foo/bar but the "importAs" attribute in my bar.metadata.json file was set to "bar".

I know this is a while ago, but how did you manage to solve this? If I change the importAs attributes manually in the generated metadata file for my built libraries indeed it works, but how can I instruct Angular CLI to use the correct (scoped) importAs statement?

With the recent version of Angular CLI (7.1.4), I might have found another workaround, which is much cleaner. You can find it in angular-demo-lazy-route-libraries repository.

Before I had a workaround with wrapper module, that statically imported lazy module and exported it. Then I used that wrapper module to configure loadChildren property in my routes.

But I accidentally discovered that (_at least with 7.1.4_) I do not need a wrapper module, instead, I can just statically import it in some file, but I do not need to import this file anywhere.
This file in the shared repo is called __fake-imports.ts in the application. Note the comment, it should never be imported.

By doing this I can keep imports in my routes simple:

{
  path: 'lazy',
  loadChildren: 'lazy-component#LazyComponentModule',
}

Can anyone test the repo as well (_more info in README.md_)? I tested it on OS X Mojave and Manjaro Linux with latest npm, node and Angular CLI.


If that indeed works and I didn't miss something, that means Angular CLI is already close to supporting this feature. The question is if they will go the extra mile.

@klemenoslaj Nice catch! Much cleaner then wrapping lib modules into app modules. For jit compilation, you can make that work with setting up the path in your tsconfig. I'm used to to this during development to build both app and lib source with webpack, see https://github.com/angular/angular-cli/issues/10369. While this is generally not recommended, it's quite convenient during development. I have a pipeline running production builds on every commit (using a separate tsconfig), so the risk for libs no being build correctly is mitigated.

I was able to test the repo and further enhance it with several (ng-packagr) sub modules. It's even working with the route config in the lib code.

@tobi-or-not-tobi, great ;) And feel free to make a pull request to my repo adding JIT and other enhancement, so that people could benefit 👍

Hi all,

This thread was opened a while ago. Having so comments makes it hard for people to find information in anything but the latest comments. But on the other hand I don't think most people would go through all of the comments anyway. New users that see this thread mostly read the first and latest comments and lose things said in between.

So for the reasons stated above (high volume of comments, hidden comments, hard to report and inform people) I'm locking this issue to prevent this comment from being lost as new comments come in.

Thank you to everyone that reported and helped diagnose the problems and provided workarounds.

Our recommended approach to lazy load a library within an angular workspace or node modules, is to use a proxy/wrapper module. With this approach lazy loading will works in both JIT and AOT mode. There are other solutions that work solely in AOT mode such as tsconfig imports however for a better development experience we don't encourage this.

Below find an example of the recommended approach;

_lazy/lazy.module.ts_

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LibModule, LazyComponent } from 'my-lib';

@NgModule({
  imports: [
    LibModule,
    RouterModule.forChild([
      { path: '', component: LazyComponent, pathMatch: 'full' }
    ])
  ]
})
export class LazyModule { }

_app.module.ts_

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';

import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
  ],
  imports: [
    RouterModule.forRoot([
      { path: '', component: HomeComponent, pathMatch: 'full'},
      { path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule'},
    ]),
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Hi all,

I am going to close this as following a convo with @alxhub. Lazy loading should happen at an application and not a library level. Hence the above approach is the recommended and the supported way to lazy load a library module in your application.

Related to: https://github.com/angular/angular/issues/31893#issuecomment-516615243

Was this page helpful?
0 / 5 - 0 ratings