Skip to content

AOT broken when using loadChildren and a function : Replace modules import by modules factories ? #4192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
maxime1992 opened this issue Jan 24, 2017 · 32 comments

Comments

@maxime1992
Copy link
Contributor

maxime1992 commented Jan 24, 2017

When I try to build my app with AOT since I'm using loadChildren with a function it breaks (not at compilation but when running the app in the browser, complaining that r.create is not a function).

According to angular/angular#13909 it seems that instead of having

image

we should rather import the factory (which is not available when coding, only when building) :

image

SamVerschueren asked if it could be replaced at build time (and opened an issue angular/angular#14005 for that).

mhevery explained why it can't be handled at build time by angular angular/angular#13909 (comment).

I'm wondering if this can be done by angular-cli ? Or as soon as we use loadChildren with a function (don't want AOT or AOT is broken due to some other bugs for example) we have to manually do that ?

@Meligy
Copy link
Contributor

Meligy commented Jan 24, 2017

I have been involved in attempts to get something like this working. The core team guys suggest it's a tough nut to crack. It's something I imagine the compiler-cli should be doing, changing the output of the compilation, it is compilation (translation) when you think about it.

For now, the workaround is to change the loadChildren assignment to a string (lazy loading), but keep the function around to make the module get included in the same bundle instead of a separate one. Keeping the exported function not just the import makes sure it doesn't get tree shaked / minified.

Like:

import { ProfileModule } from './profile/profile.module';

// Do not delete. Used to ensure ProfileModule is loaded in the same bundle.
// Referencing the function directly in `loadChildren` breaks AoT compiler.
export function loadProfileModule() {
    return ProfileModule;
}

export const routes: Routes = [
    // ... other routes ...
    { path: 'profile', loadChildren: './profile/profile.module#ProfileModule' }
];

Check the app-routing.module.ts in my routing test project for a working example:

https://github.com/meligy/routing-angular-cli

Specifically:

https://github.com/Meligy/routing-angular-cli/blob/deep-lazy-loading/src/app/app-routing.module.ts#L18

@filipesilva
Copy link
Contributor

@hansl can you weigh in?

@maxime1992
Copy link
Contributor Author

@Meligy I never thought that keeping an unused import would work for AOT !

I just tried what you proposed in my (large) app and it worked on the first try 👍 !

Thanks a lot, this is a super news while the discussion is going on 😄 !

@noamichael
Copy link

I'm attempting to use AOT with my app, which is quite large and lazy loads many modules.

I'm following the official AOT cookbook by using ngc and rollup. As was discussed in #11075, rollup does not even support lazy loading, so I'm looking for another way to compile my app.

The issue is that ngc by itself does not actually build any of my lazy loaded modules because (I'm assuming) they are not directly imported with an import statement but instead specified as a loadChildren string.

At runtime, Angular correctly requests the Factory for the module instead of the Module itself, but the module doesn't exist so the routing fails.

Is this the expected behavior of ngc? Is there no official way to compile the factories lazy loaded modules?

@Meligy
Copy link
Contributor

Meligy commented Jan 26, 2017

@noamichael AOT and lazy loading should work just fine with the build system coming builtin with the CLI.

This issue is about non-lazy-loading, and as I mentioned in previous comment, a workaround is to trick AoT to think that it's lazy-loading the module (because again, lazy-loading does work), while also having a dummy use of the module so that it still gets included in the main bundle.

@borislemke
Copy link

borislemke commented Feb 24, 2017

@Meligy I was having this issue as well and with the recommended workaround of yours my app started to work again. Could you kindly explain if we still get the benefits of lazy loading when using this workaround? How is my app being optimised when using loadChildren but still importing the module in a dummy export function?

update
After re-reading your las comment you mentioned that this is about non-lazy-loading. So what is the benefit of using this instead of just import the module with it's own routing in the main app.module?

@SamVerschueren
Copy link

@borislemke Because this way your feature module doesn't need to know what path is being used to access it.

For example, suppose you have a ContactModule. You could define the routing like this

const routes: Routes = [
	{
		path: 'contact',
		children: [
			{ path: '', component: ContactComponent },
			{ Path: ':id', component: ContactDetailComponent }
		]
	}
];

export const routing = RouterModule.forChild(routes);

This way your contact module itself defines that the URL is /contact. If you take this approach, you will define your routes like this

const routes: Routes = [
	{ path: '', component: ContactComponent },
	{ Path: ':id', component: ContactDetailComponent }
];

export const routing = RouterModule.forChild(routes);

And in your app routing, you then load the contact module as child

export const routes: Routes = [
	{ path: 'contact': loadChildren: () => ContactModule }
];

export const routing = RouterModule.forChild(routes);

This way, you contact module itself defines the routes where the contact and contact details are accessible, but your app itself defines what part comes in front of that. You contact module doesn't need to know whether it is /contact or /mycontacts.

@borislemke
Copy link

@SamVerschueren okay so based on your explanation, this has no performance benefit beside that the ContactModule is more "modular" as it does not depend on a certain path. You could just plug this into any project and define it's path, correct?

@SamVerschueren
Copy link

Yes, exactly!

@steve3d
Copy link

steve3d commented Mar 10, 2017

so, any updates here?

Does this means there is no way to use AOT with router lazy loading(loadChildren) together?

@SamVerschueren
Copy link

This has nothing to do with lazy loading.

From @Meligy

This issue is about non-lazy-loading, and as I mentioned in previous comment, a workaround is to trick AoT to think that it's lazy-loading the module (because again, lazy-loading does work), which having a dummy use of the module so that it still gets included in the main bundle.

@maxime1992
Copy link
Contributor Author

@steve3d AOT works well with lazy loading. The problem is when you want to load a module like that :

export function LoadMyModule() {
  return MyModule;
}

{
  path: '', 
  loadChildren: LoadMyModule
}

@Bil0
Copy link

Bil0 commented Apr 19, 2017

Hello,
With the recommended workaround, my feature module was lazy loaded in a separate chunk and not in the same bundle, so keeping the export function doesn't have any effect for me!

@filipesilva
Copy link
Contributor

I don't think we'll be able to correctly analyze in a static way what such a function would return.

@robounohito
Copy link

why the issue was closed? workaround from @Meligy is not working now. does anyone know how to use function with loadChildren and AOT (official way)?

@steve3d
Copy link

steve3d commented Jun 7, 2017

@robounohito , use @angular/cli it will save your life.

@robounohito
Copy link

robounohito commented Jun 7, 2017

@steve3d it's not an answer

i'm using ngtools/webpack and eagerly loaded modules

loadChildren: () => Module

won't compile with AOT

i can import module.ngfactory only for AOT and so on, but it's a mess and should be done by plugin

@colinjlacy
Copy link

colinjlacy commented Jun 21, 2017

To add to this conversation, a problem that I'm currently working on directly ties into this thread, and I can't find a solution that works with AOT.

I have an application that fetches a configuration JSON from the backend at runtime, and uses that configuration to determine how to sort feature modules into a route hierarchy. I have a demo app here: https://github.com/colinjlacy/angular-dynamic-module-loading.

The problem is that I'm running analysis on module routes to add loadChildren properties where necessary, in order to create module interconnectivity. It works pretty well in dev mode, but once I pull AOT in, it fails when I call my analysis method, rewiring my routes according to the configuration: https://github.com/colinjlacy/angular-dynamic-module-loading/blob/master/src/app/genres/genre-routes.const.ts#L23.

@filipesilva's comment above is a bit disheartening:

I don't think we'll be able to correctly analyze in a static way what such a function would return.

So I want to follow up to be completely clear. Is there any way to run analysis functions on route declarations before sending them to forRoot without breaking AOT?

EDIT: I'm following this guy as a reference: https://github.com/rangle/angular-2-aot-sandbox#func-in-routes-top and I'm not sure what the difference is. Even with exported functions defined in the same file it won't work.

@robinkedia
Copy link

Sorry what's the solution?

@goldenbearkin
Copy link

goldenbearkin commented Jan 22, 2018

@SamVerschueren

Because this way your feature module doesn't need to know what path is being used to access it.

I think what @SamVerschueren mentioned is fully justified the need of loadChildren, and of course it should be AOT-compatible. An eager loaded routed feature module must be imported by another module so that the compiler learns about its components. In this way, feature-specified routes should define up to root path.

However, the feature module routing shouldn't have knowledge from the 'upper-stream', which can

  1. prevent the name collusion problem (feature modules could have used the same pathname)
  2. also based on the 1 that it therefore achieves the truly modularized structure with fully separated concerned among modules.

Please kindly reopen this issue.

@barisbikmaz
Copy link

Any news on this ?
I've angular-cli 1.7.1 and have still the problem that I can't run AOT builds when using loadChildren with factory method.

I can switch to lazy loading by path but the my startup performance will decrease. Would be very cool if you can fix this.

@r0zar
Copy link

r0zar commented Mar 18, 2018

Also curious of status. Having to disable AoT in nativescript builds with lazy-loading children. Would be nice to get the performance boost of AoT in mobile app.

@aburht
Copy link

aburht commented Aug 14, 2018

Error: Runtime compiler is not loaded

I still see, this is broken when you run the app with aot enabled
{path: 'helper', loadChildren: () => HelperModule }

@filipesilva any idea if this would be supported anytime sooner ?

@wctiger
Copy link

wctiger commented Sep 6, 2018

Function with loadChildren is still not working with AOT as of 9/6/2018, CLI version 6.1.1 - have to switch to lazy loaded path to enable AOT and bypass error of 'Runtime compiler is not loaded'.

@unspike
Copy link

unspike commented Jan 20, 2019

Angular 7.2 still actual, no way to use AOT with loadChildren = () => ModuleName

@romko391
Copy link

I'm wondering WHY do guys develop/add new features while having such an important things broken.

@C0ZEN
Copy link

C0ZEN commented Jun 4, 2019

The CLI with ng update refactored the loadChildren since Angular 8 from a string (AOT fine) to Arrow function but the error is still here... "Error: Runtime compiler is not loaded".

What the heck ?

I undo the changes to use the string syntax and to make it works once again.

@chriszrc
Copy link

chriszrc commented Jun 26, 2019

@C0ZEN lazy loading with loadChildren and functions is working for me in 8:

@NgModule({
    imports: [
        RouterModule.forChild([{
            path: '',
            loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
        }])
    ]
})
export class MyModule { }

ref - https://blog.angularindepth.com/automatically-upgrade-lazy-loaded-angular-modules-for-ivy-e760872e6084

@devbenzy
Copy link

devbenzy commented Jun 27, 2019

@C0ZEN lazy loading with loadChildren and functions is working for me in 8:

@NgModule({
    imports: [
        RouterModule.forChild([{
            path: '',
            loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
        }])
    ]
})
export class MyModule { }

And for many others, I'd search for issues specific to that. I'd like to note that with the new import syntax, I can also now successfully use variables in the strings, and everything still works with AOT:

{
    path: APP_ROUTES.about,
    loadChildren: () => import(`./${APP_ROUTES.about}/${APP_ROUTES.about}.module`).then(m => m.AboutModule),
    data: {
      preload: false,
    },
  },

ref - https://blog.angularindepth.com/automatically-upgrade-lazy-loaded-angular-modules-for-ivy-e760872e6084
thhis works
loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)

but this doesn't
loadChildren: () => {reutrn import('./lazy/lazy.module').then(m => m.LazyModule)}

@chriszrc
Copy link

chriszrc commented Jul 2, 2019

@devbenzy Yeah, actually I was premature, the only syntax that's supported is exactly what I had in the first part of my post, (single quotes only, no template literals), see this conversation here:

#14763 (comment)

@C0ZEN
Copy link

C0ZEN commented Jul 2, 2019

Thanks for the help @chriszrc.
I used the syntax you recommended to me.
I hope they will fix it to allow all syntaxes because this is causing so much struggle for nothing important...

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests