56

I've tried loading modules without router using SystemJsNgModuleLoader, but couldn't get it to work:

this.loader.load(url).then(console.info);

I'm getting Cannot find module xxx for whatever string I use for URL (aboslute/relative urls/paths... tried many options). I looked through Router source code and couldn't find anything other then this SystemJsNgModuleLoader. I'm not even sure I should be using this...


This question was asked just yesterday at ng-europe 2016 conference - Miško & Matias answered:

Miško Hevery: One just has to get hold of the module, from there you can get the hold of component factory and you can dynamically load component factory anywhere you want in the application. This is exactly what the router does internally. So it's quite strait forward for you to do that as well.

Matias Niemelä The only special thing to note is that on the [Ng]Module there's something called entryComponents and that identifies components that could be lazy loaded - that's the entry into that component set. So when you have modules that are lazy loaded, please put the stuff into entryComponents.

...but it's not that strait forward without examples and poor docs on the subject (;

Anyone knows how to load modules manually, without using Route.loadChildren? How to get hold of the module and what exactly is the stuff that should go into entryComponents (I read FAQ, but can't try without actually loading module)?

Sasxa
  • 40,334
  • 16
  • 88
  • 102

2 Answers2

62

Anyone knows how to load modules manually, without using Route.loadChildren?

You can use SystemJsNgModuleLoader to get module's factory:

this.loader.load('./src/lazy.module#TestModule').then((factory: NgModuleFactory<any>) => {
  console.log(factory);
});

For Angular 8 see Lazy load module in angular 8

Here is how it can look like:

lazy.module.ts

@Component({
  selector: 'test',
  template: `I'm lazy module`,
})
export class Test {}

@NgModule({
  imports: [CommonModule],
  declarations: [Test],
  entryComponents: [Test]
})
export class LazyModule {
  static entry = Test;
}

app.ts

import {
  Component, NgModule, ViewContainerRef,
  SystemJsNgModuleLoader, NgModuleFactory,
  Injector} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'my-app',
  template: `<h2>Test lazy loading module</h2>`,
})
export class AppComponent {
  constructor(
    private loader: SystemJsNgModuleLoader, 
    private inj: Injector, 
    private vcRef: ViewContainerRef) {}
    
  ngOnInit() {
     this.loader.load('./src/lazy.module#LazyModule')
       .then((moduleFactory: NgModuleFactory<any>) => {
         const moduleRef = moduleFactory.create(this.inj);
         const entryComponent = (<any>moduleFactory.moduleType).entry;
         const compFactory = 
               moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);
         this.vcRef.createComponent(compFactory);
      });
  }
} 

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  providers: [SystemJsNgModuleLoader],
  bootstrap: [ AppComponent ]
})
export class AppModule {} 
this.loader.load('./src/test.module#TestModule').then((factory: NgModuleFactory<any>) => {
  console.log(factory);
});

Plunker Example

There are two options to precompile module for AOT:

  1. Angular CLI lazyModules options (since Angular 6)

Use angular/cli build-in feature:

{
  "projects": {
    "app": {
      "architect": {
        "build": {
          "options": {
            "lazyModules": [  <====== add here all your lazy modules
              "src/path-to.module"
            ]
          }
        }
      }
    }
  }
} 

See

  1. Using provideRoutes from RouterModule

app.module.ts

providers: [
  SystemJsNgModuleLoader,
  provideRoutes([
     { loadChildren: 'app/lazy/lazy.module#LazyModule' }
  ])
],

app.component.ts

export class AppComponent implements  OnInit {
    title = 'Angular cli Example SystemJsNgModuleLoader.load';

    @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

    constructor(private loader: SystemJsNgModuleLoader, private inj: Injector) {}

    ngOnInit() {
        this.loader.load('app/lazy/lazy.module#LazyModule').then((moduleFactory: NgModuleFactory<any>) => {
            const entryComponent = (<any>moduleFactory.moduleType).entry;
            const moduleRef = moduleFactory.create(this.inj);

            const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);
            this.container.createComponent(compFactory);
        });
    }
}

Github repo angular-cli-lazy


Lazy loading with webpack and AOT

Compilation using ngc

Initialization Compiler by using the following factory

export function createJitCompiler () {
    return new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();
}

Github repo

TylerH
  • 20,799
  • 66
  • 75
  • 101
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 1
    Got it to work - it was a bit tricky figuring out the exact path since I'm loading one feature module from another feature module (`src/app/one/one.module` loads `src/app/two/two.module`, and path should be `./two/two.module#TwoModule`)... Figured it out by digging through `SystemJsNgModuleLoader._compiler._ngModule.Scopes[0].Closure.routes.path` which was empty string and not `./app/one` as I expected (: Your plunker example helped. Thanks again! – Sasxa Oct 27 '16 at 20:45
  • Great example and advice! But it seems my dynamic module is not compiled unless I declare it as a route. If I don't include the module as a route, i get error says "can't find module". I'm using ng-cli. Any advice for me? :/ – KinoP Mar 15 '17 at 06:32
  • Does anyone know how to do this without SystemJS? – skusunam Mar 18 '17 at 23:44
  • 1
    @skusunam I added example for angular-cli – yurzui May 04 '17 at 14:37
  • @yurzui thx for that! Concerning the App.module example I had to add a path in the "routes" : { **path: '',** loadChildren: 'app/lazy/lazy.module#LazyModule' }... Seems to work with that. – M'sieur Toph' Aug 14 '17 at 23:07
  • 1
    @yurzui i have similar problem with SystemJsNgModuleLoader.load, i am getting cannot find module. I am trying to load the module of another webpack umd bundle as loader.load("app2/lib/app2.bundle.js#myModule") – swingmicro Aug 17 '17 at 09:11
  • @Sasxa how do you know the path. I still can handle it. Still getting cannot find module. – Hristo Enev Sep 12 '17 at 12:00
  • 3
    Found solution but I don't know if it is right solution. I had to include a route with lazy load in order for webpack to create the chunk. Which means I need to have accessible route only for this purpose. – Hristo Enev Sep 12 '17 at 13:46
  • @yurzui - did you ever find a solution. I'm trying to do the same thing with a module generated with ng-packagr with no luck – mcgraphix Sep 29 '17 at 21:11
  • Thanks for all the updates. I will try this when I'm ready, and let you know how it goes. – BBaysinger Oct 19 '17 at 23:36
  • 2
    @swingmicro did you find the solution? Loading module from external url (umd library)? – Dani Andújar Nov 10 '17 at 15:01
  • Maybe this helps: https://stackoverflow.com/questions/50149016/load-new-modules-dynamically-in-run-time-with-angular-cli-angular-5/50395048#50395048 – Michael May 23 '18 at 07:03
  • @yurzui Your solution worked for me. Thank you very much. BUT, I am also looking at passing inputs and outputs vice-versa between entry component and parent component of the dynamic component, do you have any solution? – lpradhap Jan 15 '19 at 05:39
10

[Angular 6]

Hello,

I share my solution here because I didn't find how to lazyload without router on stackoverflow .

The Yurzui's way works but he uses the Router module to compile the lazy module while I didn't want to use it.

In our src/angular.json file we can ask to the @angular/cli to compile a module apart.

For that we add the lazyModules key in "project" > "your-app-name" > "architect" > "build" > "options".

Like this :

  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects", 
  "projects": {
    "lazy-load-app": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/lazy-custom-element-app",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": [],
            "lazyModules": [
              "src/app/lazy-load/lazy-load.module",
              "src/app/another-lazy-load/another-lazy-load.module"
            ]

then we can call and load our compiled module.

Like this :

export class LoaderComponent implements OnInit {

      // tag where we gonna inject the lazyload module and his default compononent "entry"
      @ViewChild('container', { read: ViewContainerRef }) viewRef: ViewContainerRef;

      constructor(
        private loader:     NgModuleFactoryLoader,
        private injector:   Injector,
        private moduleRef:  NgModuleRef<any>,) {
      }

      ngOnInit(): void {
       this.loader.load(this.pathModuleTemplate).then((moduleFactory: NgModuleFactory<any>) => {
          // our module had a static property 'entry' that references the component that  want to load by default with the module
          const entryComponent = (<any>moduleFactory.moduleType).entry;
          const moduleRef = moduleFactory.create(this.injector);
          const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);
          this.viewRef.createComponent(compFactory);
        });
      }
}

source: https://github.com/angular/angular-cli/blob/9107f3cc4e66b25721311b5c9272ec00c2dea46f/packages/angular_devkit/build_angular/src/server/schema.json

Hoping it can help someone :)

RomainLT
  • 101
  • 1
  • 6
  • nice, can the `this.pathModuleTemplate` be a URL to the module on the server? – Andre Elrico Feb 03 '19 at 01:57
  • Sorry for the late. I don't think so but I never tried it and I'm clearly not an expert. If you try it, tell me if it works, I'm curious :) – RomainLT Mar 15 '19 at 14:49
  • thanks the module is loaded but the routes inside the module "routing module" do not load and not recognized, how do we load the routes from the lazy module ? – Mosta Mar 18 '19 at 19:11
  • I think you have to use the forChild method from RouterModule. https://angular.io/guide/lazy-loading-ngmodules *Go to "configure the feature module's routes" – RomainLT Apr 01 '19 at 13:41