ocombe / ocLazyLoad

Lazy load modules & components in AngularJS
https://oclazyload.readme.io
MIT License
2.63k stars 510 forks source link

Problem : ocLazyLoad with UI-Router and Webpack #370

Open ghost opened 7 years ago

ghost commented 7 years ago

Hi, I'm working on a project . i'm using ocLazyLoad and ui-router with webpack to lazyload module but it's not working.

angular 1.6
oclazyload  1.0.9
ui-router 0.3.2

i'm trying to resolve in my ui-router state. what i need to do ?

motin commented 7 years ago

We have the same setup and it works when adding resolves to the route configuration like follows:

                loadFileComponents: ($q, $ocLazyLoad) => {
                    return $q((resolve) => {
                        require.ensure([], () => {
                            let module = require('crud/file/components.js');
                            $ocLazyLoad.load({name: module.default.name});
                            resolve(module);
                        })
                    });
                },

Hard to understand what is not working for you. Try to supply a plunkr or sample code.

AGuyCoding commented 7 years ago

how to handle this with a typescript file?

kamilbrk commented 7 years ago

This should rather be on StackOverflow, but I hope this helps...

I don't remember where I took the example from, but the following TypeScript setup works for me, with future state definitions:

import { Ng1StateDeclaration, Transition } from '@uirouter/angularjs';

export const AdminStatePlaceholder: Ng1StateDeclaration = {
  name: 'admin.**',
  url: '/admin',
  lazyLoad: function (transition: Transition) {
    const $ocLazyLoad: oc.ILazyLoad = transition.injector().get('$ocLazyLoad');
    return System.import(/*webpackChunkName:'chunks/admin'*/'../admin/admin.module')
      .then(module => $ocLazyLoad.load(module.ADMIN_MODULE));
  }
};

then in the main file: $stateRegistry.register(AdminStatePlaceholder) and in the admin.module.ts I define $stateRegistry.register(AdminState) which points to a fully featured state definition

Note: I use System.import because ES dynamic imports do not compile properly yet I believe, even with "module": "esnext" in tsconfig.json file. That, plus the following to fix compiler errors:

// Monkey patch for tsconfig esnext imports
declare global {
  interface System {
    import (request: string): Promise<any>
  }
  var System: System
}
AGuyCoding commented 7 years ago

@kamilbrk thank you for your input. i did also use the "System.import" Regardless i have some other questions relating to your answers:


return System.import(/*webpackChunkName:'chunks/admin'*/'../admin/admin.module').then(module => $ocLazyLoad.load(module.ADMIN_MODULE));

where is the module name defined? is there a default name?


strange is also that sometimes the lazyloaded controller is loaded properly, and after some times the controller is again missing

vendor.js:274023 Error: [ng:areq] Argument 'myApp.controllers.schedulerCtrl' is not a function, got undefined


My Controller looks like this:

var schedulerModule = angular.module('schedulerModule', []);

module suiteApp.factories {

    export class schedulerFactory {

        public static $inject = ['$http'];

        constructor( private $http: any) { }

        someFunc = (mainScope) => {
            //some code
        }

    }

    schedulerModule.service("suiteApp.factories.schedulerFactory", schedulerFactory);
}

module suiteApp.controllers {

    export class schedulerCtrl {

        public static $inject = ['$scope'];

        constructor( $scope: any ) {
            //$scope.blah ... 
            //some code here
        }
    }

    schedulerModule.controller("suiteApp.controllers.schedulerCtrl", schedulerCtrl);
}

now am i suposed to rewrite EVERY controller i lazyload to this? :


var mod = angular.module('foox', []);
mod.controller('suiteApp.controllers.schedulerCtrl', ['$scope', function () {
    console.log("suiteApp.controllers.schedulerCtrl controller called");
}]);
module.exports = mod;

in order to work with ocLazyLoad? Thank you!

kamilbrk commented 7 years ago

where is the module name defined? is there a default name?

Module name is defined in the lazily imported file.

Everything within the /admin folder is in the chunks/admin webpack chunk, only loaded on demand when accessing either /admin/** URLs or admin.** states.

This is using ui-router 1.x with folder-per-module pattern and - warning - it's an exhaustive example...

/src/app/index.ts:

import * as angular from 'angular';
import { MAIN_MODULE } from './main/main.module';
angular.bootstrap(document, [MAIN_MODULE.name]);

/src/app/main/main.module.ts:

import { module } from 'angular';
import uiRouter from '@uirouter/angularjs';
import 'oclazyload';

import { AdminStatePlaceholder } from './main.states';

export const MAIN_MODULE = module('app', [uiRouter, 'oc.lazyLoad']);

MAIN_MODULE.config(['$uiRouterProvider', function ($uiRouterProvider: UIRouter) {
  const $stateRegistry = $uiRouterProvider.stateRegistry;
  $stateRegistry.register(AdminStatePlaceholder); // <- register future state definition
}]);

/src/app/main/main.states.ts:

import { Ng1StateDeclaration, Transition } from '@uirouter/angularjs';

export const AdminStatePlaceholder: Ng1StateDeclaration = { // <- future state definition
  name: 'admin.**',
  url: '/admin',
  lazyLoad: function (transition: Transition) { // <- this is where magic happens
    const $ocLazyLoad: oc.ILazyLoad = transition.injector().get('$ocLazyLoad'); // <- get hold of ocLazyLoad

    return System.import(/*webpackChunkName:'chunks/admin'*/'../admin/admin.module') // <- let webpack know to split this into a separate chunk & dynamically import it
      .then(module => $ocLazyLoad.load(module.ADMIN_MODULE)); // <- once it's loaded, add the angular module to runtime with ocLazyLoad
  }
};

/src/app/admin/admin.module.ts (lazy loaded in chunks/admin.js):

import { module } from 'angular';
import { StateRegistry } from '@uirouter/angularjs';

import { GeneralComponent } from './general/general.component';
import { AdminState, GeneralState } from './admin.states';

export const ADMIN_MODULE = module('app.admin', []); // <- angular module name defined here

ADMIN_MODULE.component('general', GeneralComponent);

ADMIN_MODULE.config(['$stateRegistryProvider', function ($stateRegistry: StateRegistry) {
  $stateRegistry.register(AdminState); // <- register the full state definition
  $stateRegistry.register(GeneralState); // <- and others alongside it
}]);

/src/app/admin/admin.states.ts (lazy loaded in chunks/admin.js):

import { Ng1StateDeclaration } from '@uirouter/angularjs';
export const AdminState: Ng1StateDeclaration = { // <- full state definition
  name: 'admin',
  url: '/admin',
  redirectTo: 'admin.general'
};
export const GeneralState: Ng1StateDeclaration = {
  name: 'admin.general',
  url: '/general',
  component: 'general'
};

/src/app/admin/general/general.component.ts (lazy loaded in chunks/admin.js):

import { GeneralController } from './general.controller';

export const GeneralComponent = {
  controller: GeneralController,
  template: `This is general settings page.`
};

/src/app/admin/general/general.controller.ts (lazy loaded in chunks/admin.js):

export class GeneralController {
  static $inject = ['$http'];
  constructor (private $http: ng.IHttpService) {
    console.log('GeneralController here');
  }
}

I must say that this example is fairly immature. I am new to TypeScript and have given up on using namespace pattern that I can see in your example (module suiteApp.controllers) in favour of "modules": "esnext", import/exports and latest techniques for everything.

I spent way too much time, unsuccessfully, trying to sort out type definitions using latest version of TS and @typings npm packages, but with "modules": "none". I'd probably recommend the same or stick to old-school setup without @types.

now am i suposed to rewrite EVERY controller i lazyload to this? :

I am not sure if you would have to rewrite all files to the format you mentioned, but with my ES2015 TS+Babel setup I sure don't have any missing controller errors. I define my controllers and other elements per file and then import them in the parent xyz.module.ts file. I just have to make sure that export/import statements are in place and webpack sorts out everything else.

ocLazyLoad website has some links to webpack-based examples in the documentation section, I'd recommend to check them out.

AGuyCoding commented 7 years ago

@kamilbrk Thank you for the examples! Did you ever tried to lazyload typescript files via webpack? It works "--watch", but the problem with that is that when you update the typescript file (lazyloaded) it loads the js file (or it gets compiled), but after that when you change the Typescript file again, webpack recognize the change and starts build successfully, but the chunk is not recompiled it is still the same chunk ? Regards, Ben!

AGuyCoding commented 7 years ago

@kamilbrk when "ChunkManifestPlugin" is in use typescript chunks dont get updated, dont know why, but this explains my question above.