Skip to content
This repository has been archived by the owner on Oct 14, 2024. It is now read-only.

support for lazy routes ? #175

Closed
kuldeepkeshwar opened this issue Sep 20, 2016 · 40 comments
Closed

support for lazy routes ? #175

kuldeepkeshwar opened this issue Sep 20, 2016 · 40 comments

Comments

@kuldeepkeshwar
Copy link
Contributor

No description provided.

@jeffwhelpley
Copy link

@kuldeepkeshwar I have not tried this out. Are you saying with the Angular Router, async routes don't work on the server side?

@kuldeepkeshwar kuldeepkeshwar changed the title support for async routes ? support for lazy routes ? Oct 1, 2016
@kuldeepkeshwar
Copy link
Contributor Author

@jeffwhelpley
sorry async was a mis-leading term, what actually I want is support for lazy routes.

on client-side , we can define using the loadChildren: () => System.import('somePath') , but I don't know how this will work on server side ?

One way to do it is to replace loadChildren with children and adding corresponding route info at the build using some loaders.

@flauc
Copy link

flauc commented Oct 1, 2016

Hi @kuldeepkeshwar I'm struggling with the same issue. I've tried this:

{path: 'gallery', loadChildren: () => new Promise(resolve => {
    (require as any).ensure([], require => {
        resolve(require('./pages/some.module').SomeModule);
    })
})},

Unfortunately it isn't working, this is the error i get: Not allowed to load local resource:

@jeffwhelpley
Copy link

Ah, yes, I see now. In short, lazy loaded routes won't work on the server side. However, I can think of a couple hacks we can do depending on your specific use case. Is the route loading completely dynamic or do you know the routes ahead of time? Just give me some more details here @kuldeepkeshwar @flauc.

@flauc
Copy link

flauc commented Oct 2, 2016

In my case i know the route ahead of time. Thank you for taking the time and helping us 👍

By the way do you know if lazy loading will be supported in angular2-universal later on?

@kuldeepkeshwar
Copy link
Contributor Author

@jeffwhelpley , yes we do know routes ahead of time . I tried to make it working ,the way I have mentioned above .
I think we should provide support for it out of box from universal. I do see a strong reason to have it asap , as Angular/webpack promotes it for perf.

@jeffwhelpley
Copy link

So, in general, there is no point to dynamically load routes on the server side and no need to lazy load. The key then is to set it up so we can lazy load on the client, but not on the server. The easiest way to do this is if you are doing lazy loading via NgModule. Then it is just a matter of building your package as one on the server or separate for the client. If you are using a different custom method, however, then I suggest trying to either use DI swapping or isServer to split your logic so that on the client you do dynamic loading while on the server you do not.

@flauc
Copy link

flauc commented Oct 3, 2016

Could you please provide a very minimal example of doing this? Just to give us an idea of what to do.

@antonyRoberts
Copy link

antonyRoberts commented Oct 7, 2016

I run into the same issue that @flauc is exhibiting with lazy Loading, any time a lazy loaded module is attempted to be accessed Chrome throws error
Not allowed to load local resource: file:///P:/UniversalSports0.index.js
webpack.config file is the same as initial other than addition of loader for scss. I've split my server and client modules, and the server has no dependency on app.routing where the lazy loaded module is called.

Have tried with
{ path: 'admin', loadChildren: './features/admin/admin.module#AdminModule' },
and
{path: 'admin', loadChildren: () => new Promise(resolve => { (require as any).ensure([], require => { resolve(require('./features/admin/admin.module').AdminModule); }) })},

Both of these result in Not allowed to load local resource: file:///P:/UniversalSports0.index.js

And when using loadChildren:()=> System.import('./features/admin/admin.module').AppModule
The first error is ERROR in ./~/systemjs/dist/system.src.js Module not found: Error: Can't resolve 'fs' in 'P:\UniversalSports\node_modules\systemjs\dist'
if we add fs:"empty" to node:{ } to fix this issue then end up with
WARNING in ./~/systemjs/index.js 9:85 Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
and in console of the browser the error is
Uncaught Error: Cannot find module "."webpackMissingModule @ index.js:103089(anonymous function) @ index.js:103089(anonymous function) @ index.js:103130__webpack_require__ @ index.js:20(anonymous function) @ index.js:103190__webpack_require__ @ index.js:20(anonymous function) @ index.js:59775__webpack_require__ @ index.js:20(anonymous function) @ index.js:107490__webpack_require__ @ index.js:20(anonymous function) @ index.js:64(anonymous function) @ index.js:67

@antonyRoberts
Copy link

antonyRoberts commented Oct 7, 2016

@gdi2290 https://github.com/antonyRoberts/UniversalLazyLoadIssue/

@kuldeepkeshwar
Copy link
Contributor Author

@jeffwhelpley using DI swapping approach will increase the bundle size as it still refers to module in the code.

@rxjs-space
Copy link

@jeffwhelpley, @gdi2290, in a universal project, when requesting a page that is loaded lazily, I got error in server like "EXCEPTION: Uncaught (in promise): ReferenceError: System is not defined". I understand that this error is caused by lazy loading and it will not affect the server from serving other eagerly loaded pages. But the error is very misleading. It makes me think there's something wrong with my code. Is there any way to suppress the error (it's several-page long) and instead prompt that the cause, for example, "lazy loading route detected. it will cause ... ... error but not affecting further running of the server.".

@PatrickJS
Copy link
Member

Looking into lazy routes in Universal. The problem is that it flickers on the client because the client is loading the route again. It works on the server and the initial render does show the lazy route. There’s also problem with Angular core’s way of preloading routes where they’re not actually waiting for the routes to load before navigating

@inzerceubytovani
Copy link

I'm not sure if I understand it correctly but when I use lazy modules with universal, they are downloaded all on first page load (instead of downloading specific module later on navigate to another lazy loaded page). It's actually problem with universal or do I understand something wrong?

Without universal, lazy loaded modules worked correctly.

@arensade
Copy link

@inzerceubytovani

open browser.module.ts and find

RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }),

and just replace with

RouterModule.forRoot([], { useHash: false}),

Link

@StefanRein
Copy link

StefanRein commented Jan 26, 2017

@gdi2290 Is there any way now to prevent the flickering? Like you said, it renders the view perfect, loads the view and shows it correctly - but in between it does flicker. Is there any workaround I can do? Anything? Thank you.

@matiishyn
Copy link

hi @StefanRein
Maybe you can try Router preloading (https://vsavkin.com/angular-router-preloading-modules-ba3c75e424cb)
it should load your lazy modules in the background and hopefully flickering will stop

I haven't tried it myself yet

@StefanRein
Copy link

StefanRein commented Jan 26, 2017

@matiishyn thank you, now it flickers only in this situation:

It does not flicker, if I initially go to the start page (let's say /home, which is included in the bundle) and the lazy modules will be loaded in the background with the PreloadAllModules strategy.

Except, I navigate directly to the /lazy route. Like @gdi2290 said, it seems the router opens something.com/lazy, then navigates client side to the "main" route something.com/, and then back again to the something.com/lazy.

Maybe it is possible to hook into some router lifecycle? CanLeave or CanActivate or something?
=> No. The View is cleared (navigated away from), before these events fire.

RouterModule.forRoot([
      { path: '', redirectTo: '/home', pathMatch: 'full' },
      { path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule'}
    ], {preloadingStrategy: PreloadAllModules})

Interesting sidenote: Giving the: {preloadingStrategy: NoPreloading}, it still preloads the other bundle with the lazy module.

@berndartmueller
Copy link

I'm facing the same issue with the flicker on directly loading lazy routes.
@matiishyn Did you come up with a solution for now?

@aitboudad
Copy link

@berndartmueller use APP_INITIALIZER

@aitboudad
Copy link

aitboudad commented Jan 31, 2017

I'm using ui-router with lazyLoad, I say resolve the promise defined in APP_INITIALIZER after the transition is completed:

export function onLoad($transition: TransitionService) {
    return () => new Promise(resolve => $transition.onSuccess({}, (transition) => resolve()));
}

@NgModule({
    ....
    providers: [
        {
            provide: APP_INITIALIZER,
            useFactory: onLoad,
            deps: [TransitionService],
            multi: true,
        },
    ],
    ...

so basically it can be applied to @angular/router

@StefanRein
Copy link

StefanRein commented Jan 31, 2017

@aitboudad thank you for the direction!

@NgModule({
    declarations: [],
    imports: [
        SharedModule,
        AppRoutingModule
    ],
    providers: [
        {
            provide: APP_INITIALIZER,
            useFactory: (injector: Injector) => {
                return () => {
                    const router: Router = injector.get(Router);
                    router.navigate(['/de-de/contact']); // => This has NO effect
                    console.log(router.url); // this logs: '/' instead of the 'domain.com/de-de/contact'
                }
            },
            deps: [Injector],
            multi: true
        }
    ]
})
export class AppModule {
}

export { AppComponent } from './app.component';

No errors, but now the above logs: '/' instead of the 'domain.com/de-de/contact' in the address bar. Can anyone tell me please why the navigate does not work? It is definitely the correct instance. Thank you very much in advance.

Edit: Funny:

...
useFactory: ...
if (isBrowser) {
    (<any>router).location.go("/de-de/contact");
}
...

Starting at (entering this URI in the browser):
example.com/de-de/lazy
Renders the correct view server side.
It redirects then to:
example.com/de-de/contact
then:
example.com/
and finally again to:
example.com/de-de/contact

It seems like, something is triggering the Router to go to the root and then again up to the initial url.

Maybe this leads anywhere:
https://angular.io/docs/ts/latest/api/router/index/Router-class.html

initialNavigation() : void
Sets up the location change listener and performs the initial navigation.

If anyone has other suggestions, please don't mind sharing.

@berndartmueller
Copy link

I just tried APP_INITIALIZER and noticed that it blocks downloading the js for the async routes. Are you aware of that?

@aitboudad

@aitboudad
Copy link

aitboudad commented Jan 31, 2017

seems @angular/router rely on APP_INITIALIZER so my proposal work only for ui-router :(,
Please note the main idea to solve this issue is to block render the view until router is fully initialized

@djabif
Copy link

djabif commented Mar 29, 2017

Hello, anyone solved this issue? I'm facing the same issue with the flicker on directly loading lazy routes.

@seklyza
Copy link

seklyza commented Jun 5, 2017

Any idea guys? I think it's a real problem and we need to think together.

@StefanRein
Copy link

@djabif @seklyza
Which version of angular are you guys using?
I heard this flickering issue was resolved with the transfer of angular universal into the angular core project. So you would need to update the project. Some documentation about this here:
https://angular.io/docs/ts/latest/guide/universal.html

But: As I know you need to implement your own rehydration. But just look at how angular universal did it: Render the data from nodeJS into a global variable server side and later on, you use this one in your browser.

@seklyza
Copy link

seklyza commented Jun 5, 2017

@StefanRein 4.1.3 (It is a project for testing Universal & Lazy Loading)

@StefanRein
Copy link

@seklyza And do you use angular universal or the new one in the core? Because these fixes weren't done in the angular universal, but in the new moved one. Please try it with the above mentioned, is it working there?

@seklyza
Copy link

seklyza commented Jun 5, 2017

I use @angular/platform-server

@StefanRein
Copy link

StefanRein commented Jul 13, 2017

@seklyza @djabif
My flickering was resolved (as from here: angular/angular#15716)
with this extra option:

{initialNavigation: 'enabled'}

in

RouterModule.forRoot([], {initialNavigation: 'enabled'})

Here https://angular.io/api/router/ExtraOptions#initialNavigation
Or here from the definition file:

/**
 * @whatItDoes Represents an option to configure when the initial navigation is performed.
 *
 * @description
 * * 'enabled' - the initial navigation starts before the root component is created.
 * The bootstrap is blocked until the initial navigation is complete.
 * * 'disabled' - the initial navigation is not performed. The location listener is set up before
 * the root component gets created.
 * * 'legacy_enabled'- the initial navigation starts after the root component has been created.
 * The bootstrap is not blocked until the initial navigation is complete. @deprecated
 * * 'legacy_disabled'- the initial navigation is not performed. The location listener is set up
 * after @deprecated
 * the root component gets created.
 * * `true` - same as 'legacy_enabled'. @deprecated since v4
 * * `false` - same as 'legacy_disabled'. @deprecated since v4
 *
 * The 'enabled' option should be used for applications unless there is a reason to have
 * more control over when the router starts its initial navigation due to some complex
 * initialization logic. In this case, 'disabled' should be used.
 *
 * The 'legacy_enabled' and 'legacy_disabled' should not be used for new applications.
 *
 * @experimental
 */
export declare type InitialNavigation = true | false | 'enabled' | 'disabled' | 'legacy_enabled' | 'legacy_disabled';

@yamidvo
Copy link

yamidvo commented Jul 15, 2017

@StefanRein Not working for me, when I don't use lazy loading I doesn't have flickering, but when I enable lazy loading the flickering appears.

RouterModule.forRoot(ROUTES, {initialNavigation: 'enabled'})

@StefanRein
Copy link

@yamidvo Do you have the latest Router and Angular Core version?
Because I have a working project. So I could minimize it and put it on Github for you guys

@yamidvo
Copy link

yamidvo commented Jul 17, 2017

@StefanRein My angular version is 4.2.5

@MarkPieszak
Copy link
Member

Hey we've updated this starter to include a CLI and a standalone webpack example using the latest @angular/platform-server that's been moved into Core. Closing this out since it's relating to the previous version, angular2-universal, but feel free to open a new issue if there's still an issue with the new version! Thanks.

@naveedahmed1
Copy link

@MarkPieszak I think the issue still exists. The {initialNavigation: 'enabled'} solution works only when we are not using Lazy Loading. When using Lazy Loading {initialNavigation: 'enabled'} prevents flickering on root url only whereas lazy loaded pages still flicker.

@MarkPieszak
Copy link
Member

Can you open up a new issue with some quick repro instructions? Just so we can differentiate it from this old issue.

@naveedahmed1
Copy link

naveedahmed1 commented Sep 25, 2017

@MarkPieszak I think there will always be some flickering in cases similar to below code:

        public userDetails: any;
        ngOnInit() {
                .....
                .....
                this.userService.getUserDetails(id).subscribe(result => {
                    this.userDetails = result;
                });
                ....
                ....
        }
<div *ngIf="userDetails">
 <user-details user="userDetails"><user-details>
</div>

And I think the reason is, even when the SSR send response with user details, when this component initializes on client side it sets userDetails to null which removes user-details from dom, and when userService returns users details (even from cache) it will be added to dom again. If the flicker is noticeable to the user or not would depend on how complex the user-details component is.

@lucaswxp
Copy link

@naveedahmed1 Hey, did you solve it?

Any news on this?

@naveedahmed1
Copy link

These are supported now and should work without any issue. I would refer you to the thread angular/angular#23427 specifically my comment in the end angular/angular#23427 (comment)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests