Skip to content

(bug)router/AOT Unable to use feature-modules in routing when doing AOT compilation #11075

Closed

Description

@choeller

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
We are using Features-Modules to encapsulate things belonging together and we are using theses Features-Modules in Routing like this:

app.module.ts

@NgModule({
  imports: [SharedModule.forRoot(),
            BrowserModule,
            RouterModule.forRoot(APP_ROUTES)
  ],
  ...
})
export class AppModule {
}

app.routes.ts:

export function getTasksModule() {
  return TasksModule;
}

export const APP_ROUTES: Routes = [
  ...
  {path: 'tasks', canLoad: [LoginGuard], loadChildren: getTasksModule},
];

this works fine when not doing AOT. Now when calling

./node_modules/.bin/ngc -p ./src

the application compiles without errors, but in runtime I get the following exception when enterting the /tasks route:

Error: Uncaught (in promise): TypeError: r.create is not a function
    at resolvePromise (zone.js:558)
    at zone.js:535
    at ZoneDelegate.invoke (zone.js:332)
    at Object.NgZoneImpl.inner.inner.fork.onInvoke (ng_zone_impl.js:41)

after some debugging it looks like the application get the TasksModule where it needs to have the TasksModuleNgFactory. So the exception happens in this code:

router_config_loader.ts

  load(parentInjector: Injector, loadChildren: LoadChildren): Observable<LoadedRouterConfig> {
    return this.loadModuleFactory(loadChildren).map(r => {
      const ref = r.create(parentInjector);
      return new LoadedRouterConfig(
          flatten(ref.injector.get(ROUTES)), ref.injector, ref.componentFactoryResolver);
    });
  }

and r is TasksModule when the exeption occurs.

Expected/desired behavior

It should be possible to use loadChildren in combination with AOT

What is the motivation / use case for changing the behavior?

Currently it's not possible to encapsulate routing into Feature-Modules when using the AOT-compiler

Please tell us about your environment:

  • Angular version: commit 2b313e4
  • Browser: [all ]
  • Language: [all ]

Activity

  1. changed the title [-](bug)router/AOT unable to use feature-modules in routing when doing AOT compilation[/-] [+](bug)router/AOT Unable to use feature-modules in routing when doing AOT compilation[/+] on Aug 25, 2016
  2. qdouble commented on Sep 2, 2016

    @qdouble

    I just encountered this. The bug is still present using the latest incremental builds.

  3. hansl commented on Sep 6, 2016

    @hansl
    Contributor

    Could you try to use a string to the module instead of a function for loadChildren?

    Also, the CLI is broken right now until #11387 is in and released.

  4. choeller commented on Sep 6, 2016

    @choeller
    ContributorAuthor

    When trying to pass in a string angular seems to try to lazy load although I'm using webpack with a single bundle. (with my setup this fails as it's missing System.JS I think) I don't need lazy loading (nor would I know how to setup my bundles correctly ;)) so using a function to load the module worked perfectly fine for me.

  5. qdouble commented on Sep 6, 2016

    @qdouble

    @hansl how would you load it with a string?

    when I try to put something like { path: 'lazy', loadChildren: './features/lazy#LazyModule' } that does't seem to work at all with webpack.

    Both of the ones below work fine in JIT, but not AOT.

    export function loadLazy() {
      return require('es6-promise!./features/lazy')('LazyModule');
    }
    
    //or
    
    export function loadLazy() {
      return System.import('./features/lazy').then((r: any) => r.LazyModule);
    }
    

    Is there any current solution for us that are using Webpack 2, but not CLI?

  6. hansl commented on Sep 6, 2016

    @hansl
    Contributor

    @choeller it fails because it uses System.import(), yes, but not because you're missing SystemJS. SystemJS is a library that implements a polyfill of System.import(), but normally webpack is able to detect calls to it and refactor the code to use an internal webpack-specific implementation. In the current RC6 the bug is that webpack is not smart enough to detect the call to System.import() and leave the code as is, resulting in a real call to a non-existant System.

    There are many ways to fix it, but there's only 2 ways that work with the CLI:

    1. using a function that manually calls System.import(), similar to what @qdouble posted in his second example. You could also use the first one but it's more reliant on webpack and might require more typing.
    2. use a string and the nightly build of Angular which has fix(lazy-loading): fix an issue with webpack and lazy loader. #11387 in it.

    There is a few other solutions but they're way more complicated. The truth is, when Angular RC6+1 comes around with bug fixes that bug will be fixed in Angular. Until then, you're stuck with a manual solution, at least temporarily.

  7. qdouble commented on Sep 6, 2016

    @qdouble

    @hansl I'm a bit confused by your reply, it seems you are not specifically addressing the issue as it pertains to AOT. You said the export functions work are a solution, but they do not work properly in AOT mode. It compiles fine, but when you actually navigate to the route it gives the t.create is not a function error.

    I'm using the latest nightly builds by the way.

  8. hansl commented on Sep 6, 2016

    @hansl
    Contributor

    That's because Lazy Loading changes with AoT. You're not loading a module anymore, but a module factory.

  9. qdouble commented on Sep 6, 2016

    @qdouble

    @hansl so the solution would be that we have to point to somewhere different when the module is loaded lazily? Like in JIT it'd point to LazyModule and in AOT we'd set it to point to LazyModuleNgFactory? Or is there some other workaround we need to implement?

  10. qdouble commented on Sep 6, 2016

    @qdouble

    @hansl Okay, I got what you are saying now...if I point directly to the ngc generated factory, it lazy loads fine. Seems that unless you have a plugin that knows where your tsconfig output is going and that can parse your existing module, it could be a bit tedious to set both...but assuming you don't have a boat load of lazy loaded modules, doing it manually isn't torture I suppose.

  11. hansl commented on Sep 7, 2016

    @hansl
    Contributor

    If you use a string, the module loader will inject an ngfactory in the string before calling system.import. That's why you don't need to change anything in that case.

    One way we've been doing AoT is by running the ngc tool ourselves and then using the cli to compile. Rob Wormald should be close to be done with integrating AoT in the CLI. That will also include code to support lazy loading.

  12. qdouble commented on Sep 7, 2016

    @qdouble

    @hansl could you give me an example of return System.import('./features/lazy').then((r: any) => r.LazyModule); into a valid string? I'm having issues getting it to work, not quite sure how it's supposed to look.

  13. 19 remaining items

  14. SamVerschueren commented on Jan 11, 2017

    @SamVerschueren
    Contributor

    It's not angular CLI specific. I never used the CLI and I have the same issues.

    I created a quick example here.

    1. git clone https://github.com/SamVerschueren/ng2-hello-world-routing
    2. cd ng2-hello-world-routing
    3. git checkout load-children
    4. npm install

    If you run npm start, you will notice that you can navigate from Home to Contact and back while it uses the approach mentioned in the initial post.

    Now if you build it with npm run build, it will output a production bundle in the builds directory. So navigate to that directory and start a local server (http-server, lite-server, ...). Open the url and open devtools. If you click on Contact, it will fail to load and shows an error about r.create not being a function.

  15. DzmitryShylovich commented on Jan 11, 2017

    @DzmitryShylovich
    Contributor

    @SamVerschueren rollup doesn't support lazy loading

  16. SamVerschueren commented on Jan 11, 2017

    @SamVerschueren
    Contributor

    @DzmitryShylovich You pinged the wrong Sam ;).

    Yes I know, but this issue isn't about lazy loading. Lazy loading happens when you use a string literal as value of the loadChildren property.

    This issue is about passing a function that returns a module to the loadChildren property. For example, the app.routing.ts from my example

    import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
    
    import { HomeComponent } from './home/home.component';
    import { ContactModule } from './contact/contact.module';
    
    export function loadContactModule() {
    	return ContactModule;
    }
    
    export const routes: Routes = [
    	{ path: '', component: HomeComponent, pathMatch: 'full' },
    	{ path: 'contact', loadChildren: loadContactModule }
    ];
    
    export const routing = RouterModule.forRoot(routes);

    Everything is in one bundle. The main benefit is that every route inside the contact part of your application starts with /contact and you can easily change that.

    So yes, rollup doesn't work with lazy loading but like said in the beginning, this is not the same.

  17. DzmitryShylovich commented on Jan 11, 2017

    @DzmitryShylovich
    Contributor

    You pinged the wrong Sam ;).

    sorry, github didn't suggest your name for some reason :)

  18. kpudlik commented on Jan 13, 2017

    @kpudlik

    +1

    Using loadChildren() is the only way to follow the NgModule encapsulation conception (because it encapsulates routes too). Without using loadChildren there is a huge mess with different conventions... It should work with AoT out of the box...

  19. DzmitryShylovich commented on Jan 13, 2017

    @DzmitryShylovich
    Contributor

    You need to return NgModuleFactory instead of NgModule, because we cannot compile it at runtime (compiler is not available after aot).

    Example is here #13909

    Until pr will be merged you need to cast factory to any:

    export function loadProfileModule() {
    	return <any>ProfileModuleNgFactory;
    }
    

    You should open a feature request in cli repo to automatically convert NgModule into NgModuleFactory during compilation, but for other build tools you need to do this manually.

  20. added 2 commits that reference this issue on Jan 18, 2017
    635bf02
    2af5862
  21. added a commit that references this issue on Feb 12, 2017
    fac2a07
  22. baisui1981 commented on Aug 12, 2017

    @baisui1981

    @SamVerschueren i have fixed it, as u mentioned
    `
    import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

    import { HomeComponent } from './home/home.component';
    import { ContactModule } from './contact/contact.module';

    export function loadContactModule() {
    return ContactModule;
    }
    export const routes: Routes = [
    { path: '', component: HomeComponent, pathMatch: 'full' },
    { path: 'contact', loadChildren: loadContactModule }
    ];
    export const routing = RouterModule.forRoot(routes);change the ContactModule To ContactModuleNgFactoryimport { Routes, RouterModule, PreloadAllModules } from '@angular/router';

    import { HomeComponent } from './home/home.component';
    import {ContactModuleNgFactory} from "../../aot/src/user/contact.module.ngfactory";

    export function loadContactModule() {
    return ContactModuleNgFactory;
    }
    export const routes: Routes = [
    { path: '', component: HomeComponent, pathMatch: 'full' },
    { path: 'contact', loadChildren: loadContactModule }
    ];
    export const routing = RouterModule.forRoot(routes);in order to make aot compile success,create a new file named “app.module-aot.ts” and also should modify the element "files" which is aot compiler entry in "tsconfig-aot.json""files": [
    "src/app.module.ts",
    "src/main.ts"
    ],
    to
    "files": [
    "src/contact/contact.module.ts",
    "src/app.module-aot.ts",
    "src/main.ts"
    ]
    `
    tested, that is effective

  23. added a commit that references this issue on Aug 28, 2017
    ebc9b58
  24. angular-automatic-lock-bot commented on Sep 12, 2019

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  25. locked and limited conversation to collaborators on Sep 12, 2019
  26. added a commit that references this issue on Apr 27, 2021
    d622a78
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions