Skip to content
This repository has been archived by the owner on Feb 9, 2018. It is now read-only.

ui-router ng2 and Angular 4: almost got lazy loading working...need help #33

Closed
mattiLeBlanc opened this issue May 21, 2017 · 10 comments
Closed

Comments

@mattiLeBlanc
Copy link

Hi

I am working my way through the angular 4 tutorial NgModules (https://angular.io/docs/ts/latest/guide/ngmodule.html#!#root-module) and I am implementing this in my Meteor App. This all works fine, I just ran into an issue converting the Lazy Load as featured in the Quickstart (https://github.com/ui-router/quickstart-ng2/blob/master/src/bootstrap.ts).

I think the culprit is the

 { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader 

where my browser is complaining

modules.js?hash=40a315e…:75443 ReferenceError: System is not defined

when I try to transition to my lazy loaded Crisis component.
I am following the BAZ example combined with Angular 4 Crisis example.

Should I just add SystemJS (why would this be necessary for lazy loading anyway).

You can see my app.module at
https://bitbucket.org/mattijsspierings/angular4-meteor/src/7a318d1b910576f7d81ed8bdf8466fa9371d89ca/client/imports/?at=router. Is should be publicly available.

Hope anyone can point me in the right direction.

@christopherthielen
Copy link
Member

Sounds like you need to include SystemJS bundle

https://unpkg.com/systemjs/dist/system.js

@christopherthielen
Copy link
Member

Also note that lazy loading has changed A LOT since I coded this quickstart.

You may want to also check out the sample app for Angular which is based on the Angular-CLI

@christopherthielen
Copy link
Member

I updated the quickstart-ng2 to angular 4 and fixed lazy loading.

TL;DR change name: 'app.baz' to name: 'app.baz.**' to mark it as a future state

@mattiLeBlanc
Copy link
Author

@christopherthielen Thanks mate. I will give it a try.

@mattiLeBlanc
Copy link
Author

Is there a way not to have to include SystemJs? I am using MeteorJS and I am not sure if that is based on CommonJS or something else, but it seems weird to have to use SystemJS just to enable the Lazy Loading. Is this really necessary? I see that the example on the ng4 website also uses the SystemJS lib.

@christopherthielen
Copy link
Member

Is there a way not to have to include SystemJs?

Frankly, I don't know. System.import() is as close as we had to a defacto standard for lazy loading code. Note that this doesn't necessarily mean you have to include SystemJS binaries. For example, webpack changes () => System.import() calls into code split points and injects its own lazy loading code instead.

If meteor includes a module loader you could try to use that instead of SystemJS. In some manner you must fetch and load the sources before the NgModule can be lazy loaded into Angular.

@mattiLeBlanc
Copy link
Author

@christopherthielen Hi, Meteor 1.5 added the package dynamic-import which according one of the Meteor guys can help with lazy loading.


import { AppComponent } from "./app.component";
import { Ng2StateDeclaration, loadNgModule } from "@uirouter/angular";


export function loadCrisisModule() {
  return module.dynamicImport( './common/crisis/crisis.module' ).then(( { CrisisModule } ) => {
    console.log( CrisisModule );
    return CrisisModule;
  } );
}
export function loadContactModule() {
  return module.dynamicImport( './common/contact/contact.module' ).then(( { ContactModule } ) => ContactModule );
}
/** The top level state(s) */
export let MAIN_STATES: Ng2StateDeclaration[] = [
  // The top-level app state.
  // This state fills the root <ui-view></ui-view> (defined in index.html) with the AppComponent
  { name: 'app', component: AppComponent },
  { name: 'app.contact', url: '/contact', lazyLoad: loadContactModule },
];

When I use the loadContactModule (or crisisModule loader) I can see the promise resolve since the console.log kicks in and dumps the CrisisModule component source. However, Angular doesn't seem to pick it up and render the state in my ui-view. According to the docs https://ui-router.github.io/ng2/docs/latest/interfaces/state.lazyloadresult.html you are expecting a promise, right.
Any idea how to get the state to render in my ui-view? I think I am almost there.

@IvanGarzon
Copy link

I was having the same issue with the lazyLoading states that's how I got to this thread. My project is build it using WebpackJS instead of SystemJS.
First of all, thanks for the heads up, you pointed me in the right direction.

Regardless, with the MAIN_STATES file :

import { AppComponent } from "./app.component";
import { Ng2StateDeclaration, loadNgModule } from "@uirouter/angular";

/** Added ContactModule */
import { ContactModule } from "./common/contact/contact.module"

/** The top level state(s) */
export let MAIN_STATES: Ng2StateDeclaration[] = [
  // The top-level app state.
  // This state fills the root <ui-view></ui-view> (defined in index.html) with the AppComponent
  { name: 'app', component: AppComponent },

  // You need to mark app.contact.** as a future state
  // You might not need to use dynamicImport from MeteorJS
  { name: 'app.contact.**', url: '/contact', lazyLoad: loadNgModule( () => ContactModule },
];

In your app.component.html ------> important -----> uiSref="app.contact"

<a uiSref="app.contact" [uiOptions]="{ inherit: false }"  uiSrefActive="active" role="button">
    <span>Contact</span>
</a>

The lazy loaded NgModule must have a state named 'app.contact' which replaces the (placeholder) 'app.contact.**' Future State. Add a 'app.contact' state to the lazy loaded NgModule using UIRouterModule.forChild({ states: CONTACT_STATES }).)

Then, in CONTACT_STATES file

import { Ng2StateDeclaration, Transition } from "@uirouter/angular";
import { ContactComponent } from "./contact.component";
import { ContactDetailComponent } from "./contact-detail.component";

export let CONTACT_STATES: Ng2StateDeclaration[] = [

// A state for the 'app.contact' submodule.
{
    name: "app.contact",
    url: "/contact",
    component: ContactComponent
}

// A child state of app.jokes
{
    name: "app.contact.details",
    url: "/:contactId",
    component: ContactDetailComponent
}
];

And lastly, do not forget import the CONTACT_STATES in your ContactModule

import { NgModule } from "@angular/core";
import { UIRouterModule } from "@uirouter/angular";

import { ContactComponent } from "./contact.component";
import { ContactDetailComponent } from "./contact-detail.component";

import { CONTACTS_STATES } from "./contact.states";

@NgModule({
  imports: [
    UIRouterModule.forChild({ states: CONTACT_STATES })
  ],
  declarations: [
    ContactDetailComponent,
    ContactComponent
  ],
  providers: []
})
export class ContactModule {}

Hopefully this helps to fix your issue @mattiLeBlanc. cheers.

@mattiLeBlanc
Copy link
Author

@IvanGarzon Hi mate,

Thanks for your reply.

I think you are mistaken though. It's my believe that by doing a import { ContactModule } from "./common/contact/contact.module" in the Mainstates file and then using loadNgModule it is actually not lazy loading it. In all the example I looked at the lazyload requires a promise and not an import of an module.
I tried it anyway and got an error that app.contact was already defined, which is true since it is defined in the contact module as a forChild definition.

I think the dynamicImport is the way to go since I need something that loads the module dynamically (this is what System did).

Thanks anyway for trying to help me out.

@IvanGarzon
Copy link

// Angular Router
loadChildren: () => require("./contact/contact.component")["ContactModule"];
or
loadChildren: 'contact/contact.component#ContactModule'

// MeteorJS
export function loadContactModule() {
  return module.dynamicImport( './common/contact/contact.module' ).then(( { ContactModule } ) => ContactModule );
}

// Webpack
/** Lazy Modules */
import { ContactModule } from "./contact/contact.module";
loadNgModule( () => ContactModule

All these three approaches are pretty much equivalent if I am not mistaken.

The concept of lazy loading helps us decrease the startup time of the application. With lazy loading our app does not need to load everything at once, it only needs to load what the user expects to see when the app first loads. Modules that are lazily loaded will only be loaded when the user navigates to their routes, this is why we don not declare them in the main module.

Having said this you only need to remove ContactModule from your imports in your AppModule. Also I forgot to mention that in your AppModule you can still use ----> providers: [{provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader}]

Ps: You can notice that the contact module has been lazily loaded if you check the state in the ui-router visualizer. @mattiLeBlanc

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

3 participants