Skip to content

Instantly share code, notes, and snippets.

@brandonroberts
Last active April 26, 2019 12:40
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save brandonroberts/02cc07face25886fe142c4dbd8da1340 to your computer and use it in GitHub Desktop.
Save brandonroberts/02cc07face25886fe142c4dbd8da1340 to your computer and use it in GitHub Desktop.
Webpack Async NgModule Loader
import {Injectable, NgModuleFactory, NgModuleFactoryLoader, Compiler, Type} from '@angular/core';
class LoaderCallback {
constructor(public callback) {}
}
export let load: Type = (callback: Function) => {
return new LoaderCallback(callback);
};
/**
* NgModuleFactoryLoader that uses Promise to load NgModule type and then compiles them.
* @experimental
*/
@Injectable()
export class AsyncNgModuleLoader implements NgModuleFactoryLoader {
constructor(private compiler: Compiler) {}
load(modulePath: string|LoaderCallback): Promise<NgModuleFactory<any>> {
if (modulePath instanceof LoaderCallback) {
let loader = (modulePath as LoaderCallback).callback();
return Promise
.resolve(loader)
.then((type: any) => checkNotEmpty(type, '', ''))
.then((type: any) => this.compiler.compileModuleAsync(type));
}
return Promise.resolve(null);
}
}
function checkNotEmpty(value: any, modulePath: string, exportName: string): any {
if (!value) {
throw new Error(`Cannot find '${exportName}' in '${modulePath}'`);
}
return value;
}
import {NgModuleFactoryLoader} from '@angular/core';
import {AsyncNgModuleLoader} from './async-ng-module-loader';

// Add to main providers
{provide: NgModuleFactoryLoader, useClass: AsyncNgModuleLoader}
import {load} from './async-ng-module-loader';

{
  path: 'lazy',
  loadChildren: load(() => new Promise(resolve => {
      (require as any).ensure([], require => {
        resolve(require('./lazy-module').LazyModule);
      })
    }))
},
@iurii-kyrylenko
Copy link

@brandonroberts, thank you for the gist!
I slightly modified/refactored your solution in my startup template.
To the load function I added the second optional parameter exportName. When exportName is missed - it's implied that module has the default export.
This is a my routing and provider registration

@iurii-kyrylenko
Copy link

iurii-kyrylenko commented Aug 13, 2016

@hassansad, @400k

I get Error: path.split is not a function

Did you register provider?

{provide: NgModuleFactoryLoader, useClass: AsyncNgModuleLoader}

@brandonroberts
Copy link
Author

@iurii-kyrlenko nice! Even you want something less verbose, look into the es6-promise-loader: https://www.npmjs.com/package/es6-promise-loader. It lets you provide a path and namespace also.

@iurii-kyrylenko
Copy link

iurii-kyrylenko commented Aug 14, 2016

Thank you, @brandonroberts!
I cannot pass namespace (export name) as a function parameter when defining routes:

{ path: 'task2', loadChildren: require('es6-promise!./task-2/task2.module')('Task2Module') }

because it's called immediately and breaks the 'laziness'. So, I still have to use a helper load:

{ path: 'task2', loadChildren: load(require('es6-promise!./task-2/task2.module'), 'Task2Module') }

which calls the function in NgModuleFactoryLoader#load.

@hassanasad
Copy link

Ah Thanks @iurii-kyrylenko - I actually didn't register the provider. Now it works after i registered it in the main app.module.ts file.

You are the man !! :)

@benetis
Copy link

benetis commented Aug 17, 2016

Awesome gist!

@brandonroberts
Copy link
Author

@iurii-kyrylenko that is correct. You still need to wrap it in the load function for now. Once RC6 lands, you'll be able to use the require function directly

@a5hik
Copy link

a5hik commented Aug 25, 2016

Works Perfect! Awesome work!

@DarienF
Copy link

DarienF commented Aug 29, 2016

I notice: in my project there two modules with children module integration. Webpack build without errors but I find two new *.js files in root folder: 1.js and 4.js Should I import this files into project befor publish or this files imported into main.js......?

@DarienF
Copy link

DarienF commented Aug 29, 2016

I have some difficulties with location were I should register provider. So for others who try implemet this solution...in app.module.ts:

 providers: [
        { provide: NgModuleFactoryLoader, useClass: AsyncNgModuleLoader },
       ...
    ]

@stefanaerts
Copy link

i the routermodule

@stefanaerts
Copy link

DarienF, put it in the router module.

@jrohatiner
Copy link

Thank You!
jr - 1

@lialosiu
Copy link

lialosiu commented Aug 30, 2016

EXCEPTION: Error: Uncaught (in promise): TypeError: loadChildren is not a function

I got error like this... ^

do I miss something?

at app.routing.ts

const routes: Routes = [
    {
        path        : 'dev',
        loadChildren: load(() => new Promise(resolve => {
            return (require as any).ensure([], (require: any) => {
                return resolve(require('./pages/dev/dev.module').default);
            });
        }))
    },
    {
        path     : '',
        component: HomeComponent
    }
];

at app.module.ts

    providers   : [
        {provide: NgModuleFactoryLoader, useClass: AsyncNgModuleLoader},
        AuthGuard,
        SessionService,
    ],

@lghiur
Copy link

lghiur commented Aug 30, 2016

Hello @brandonroberts,

I'm having same issue as @lialosiu, can you please provide some advise?

Thank you,
Laurentiu

@brandonroberts
Copy link
Author

@lialosiu @lghiur are you importing the load function from the async-ng-module-loader.ts file?

@forsak3n
Copy link

loadChildren: () => new Promise(resolve => {
            return (require as any).ensure([], (require: any) => {
                return resolve(require('./pages/dev/dev.module').default);
            });
        })

did the trick for me. Notice the absence of load()

@brandonroberts
Copy link
Author

@forsak3n yes, that will work once RC6 lands and you won't need the loader anymore at all 😄

@samfajar
Copy link

samfajar commented Sep 1, 2016

it's work, awesome gist. more thanks for you. 👍

@b27lu
Copy link

b27lu commented Sep 1, 2016

I get a "Generic type 'Type' requires 1 type argument(s)." after upgrading to rc6. Replacing Type by Type<any> won't fix it.

Just tried @forsak3n s' solution, my routes is like

export const loginRoutes: Routes = [
    {
        path: 'login',
        loadChildren: () => new Promise(resolve => {
            return (require as any).ensure([], (require: any) => {
                return resolve(require('./login.module/login.module').LoginModule);
            });
        })
    }];

    export const loginRouting = RouterModule.forChild(loginRoutes);

then, I get "Type '() => Promise<{}>' is not assignable to type 'string'."

@iurii-kyrylenko
Copy link

The following works for my startup in RC6:

    { path: 'task2', loadChildren: () =>
        new Promise(resolve =>
            (require as any).ensure([], () =>
                resolve(require('./task-2/task2.module').Task2Module)
            )
        )
    }

@AnotherStop, have you updated your typings.json?

@lghiur
Copy link

lghiur commented Sep 2, 2016

Hi @iurii-kyrylenko,

Can you please help me a bit as I'm out of solutions. I have tried your solution but I'm getting "No NgModule metadata found for '[object Promise]'" error. I want to mention that I don't use Typescript. I have placed bellow the code I'm using:

  • router
{
  path: 'style-guide',
  loadChildren: () =>
    new Promise(resolve => {
      return require.ensure([], () =>
        resolve(require('./style-guide.module').StyleGuideModule)
      );
    }

-style guide module

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

import StyleGuideComponent from './style-guide.component';

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
        { path: '', component: StyleGuideComponent }
    ])
  ],
  declarations: [
    StyleGuideComponent
  ]
})
export class StyleGuideModule {}

Thank you,
Laurentiu

@iurii-kyrylenko
Copy link

@lghiur,

I don't use Typescript

It's strange, because you're using @NgModule decorator function. This is typescript syntax.
Anyway, check tsconfig.json: a compiler option emitDecoratorMetadata should be set to the true.

@lghiur
Copy link

lghiur commented Sep 4, 2016

@iurii-kyrylenko I am transpiling my code with babel and I m using a decorator plugin. That s why I can use decorators without Typescript and I don t have a tsconfig file.

Thank you
Laurențiu

@qk44077907
Copy link

the same issue with @lialosiu

@brandonroberts
Copy link
Author

@qk44077907 @lialosiu are you still having this issue? With RC6 you don't need a custom loader anymore. I also have a webpack loader here you can use if you want to use string-based lazy loading: https://www.npmjs.com/package/angular2-router-loader

@satish-pokala
Copy link

satish-pokala commented Sep 13, 2016

hello can anyone help me,

Work this issue with RC5.

{
path: 'admin',
loadChildren: load(() => new Promise(resolve => {
return (require as any).ensure([], (require: any) => {
return resolve(require('./pages/dev/dev.module').default);
});
}))
},

because of require cannot find name.

@mineofcode
Copy link

mineofcode commented Nov 5, 2016

export let load: Type = (callback: Function) => {
return new LoaderCallback(callback);
};

Generic type 'Type' requires 1 type argument(s)

Getting above error while compile with ng serve

node -v 7.0.0
npm -v 3.10.8
angular-cli: 1.0.0-beta.19-3
editor : Visual Studio Code

please help me...

"dependencies": {
"@angular/common": "~2.1.0",
"@angular/compiler": "~2.1.0",
"@angular/core": "~2.1.0",
"@angular/forms": "~2.1.0",
"@angular/http": "~2.1.0",
"@angular/platform-browser": "~2.1.0",
"@angular/platform-browser-dynamic": "~2.1.0",
"@angular/router": "~3.1.0",
"bootstrap": "^3.3.7",
"core-js": "^2.4.1",
"jquery": "^3.1.1",
"rxjs": "5.0.0-beta.12",
"ts-helpers": "^1.1.1",
"zone.js": "^0.6.23"
},

Copy link

ghost commented Mar 22, 2017

Same error than @masagatech trying to fix it right now.

@haisaco
Copy link

haisaco commented Apr 11, 2017

same error @masagatech trying to fix it right now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment