Lazy loading Angular directives in ui-router as Webpack chunks - javascript

I am trying to lazy load an Angular directive as a webpack chunk.
Here is my current config attempt at using ocLazyLoad:
// Basic Config
function routingBase( $urlRouterProvider, $locationProvider, $stateProvider ) {
$locationProvider.html5Mode(true);
$stateProvider
.state('home', {
url: '/home',
template: '<app-main></app-main>',
resolve: {
load: ( $q, $ocLazyLoad ) => {
let deferred = $q.defer();
require.ensure([], (require) => {
// Load entire module
let module = require('../modules/main');
$ocLazyLoad.load({
name: module.name
});
deferred.resolve(module);
}, 'app-main');
return deferred.promise;
}
}
})
}
This goes in myModule.config(routingBase);.
../modules/main is just an angular module that exports a directive (e.g. export default angular.module('main',[]).directive('appMain', appMainFn);.
Any tips? What I am getting is that the <app-main></app-main> is correctly added to the document, and that the chunk is correctly loaded as module. But it is not replaced (it stays as <app-main></app-main>).
Would you recommend a different method for lazy loading chunks (maybe using $compileProvider)? I would like the cleanest possible way.
Thank you very much for your help.

I was able to get this working in my current project. Just change the following line and see it works. You need to add default as well since you are exporting angular module as default. Cheers!
$ocLazyLoad.load({
name: module.default.name
});

Related

AngularJS undefined Controller using require

I am using requireJS in my application.
Whenever i tried to register controller on my module it said that the controller is not defined. Here is my controller which resides on login.controller.js
function LoginController() {
}
and here's my module code:
require('angular')
require('#uirouter/angularjs');
require('./service/storage')
require('./controller/login.controller')
angular.module('SecurityModule', ['ui.router'])
.controller('LoginController', LoginController);
// Routing
angular.module('SecurityModule')
.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
$locationProvider.hashPrefix('');
$stateProvider.state('login', {
url: '/login',
templateUrl: '/app/resources/view/security/login.html',
controller: 'LoginController',
});
})
;
When i checked my bundled.js the declaration of LoginController appears first. So why is it still undefine?
Thanks.
NOTE that im using browserify (which then uses commonJS) to bundle my files.
As the documentation states:
A module is a collection of configuration and run blocks which get
applied to the application during the bootstrap process. In its
simplest form the module consist of collection of two kinds of blocks:
Configuration blocks - get executed during the provider registrations
and configuration phase. Only providers and constants can be injected
into configuration blocks. This is to prevent accidental instantiation
of services before they have been fully configured.
angular.module('myModule', []).
config(function(injectables) { // provider-injector
// This is an example of config block.
// You can have as many of these as you want.
// You can only inject Providers (not instances)
// into config blocks.
}).
run(function(injectables) { // instance-injector
// This is an example of a run block.
// You can have as many of these as you want.
// You can only inject instances (not Providers)
// into run blocks
});

Lazy loading Angular modules with Webpack

I'm trying to get lazy-loaded Angular modules working with Webpack, but I'm having some difficulties. Webpack appears to generate the split point correctly, because I see a 1.bundle.js getting created that contains the code for the child app, but I don't see any request for 1.bundle.js when I load the page, and the child app doesn't initialize. The console.log never seems to fire, and it doesn't even appear to get to the point where $oclazyload would initialize the module.
There are a few points where I am confused.
1) Will webpack make the request to the server, or do I have to load the second bundle manually? (I've tried both, and neither works)
2) If I do need to load the bundles manually, in what order should they be loaded?
3) The third argument to require.ensure supposedly lets you control the name of the bundle, but the bundle is named 1.bundle.js no matter what string I pass.
4) Why can't I step through the code inside the require.ensure block in the debugger? When I do so I end up looking at this in the Chrome source view:
undefined
/** WEBPACK FOOTER **
**
**/
(Code Below)
Main entry point code:
'use strict';
import angular from 'angular';
import 'angular-ui-router';
import 'oclazyload';
angular.module('parentApp', [
'ui.router',
])
.config(['$urlRouterProvider', '$locationProvider', ($urlRouterProvider, $locationProvider) => {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
}])
.config(['$stateProvider', ($stateProvider) => {
$stateProvider
.state('child-app', {
url: '/child-app',
template: '<child-app></child-app>',
resolve: {
loadAppModule: ($q, $ocLazyLoad) => {
return $q((resolve) => {
require.ensure(['./child-app/app.js'], (require) => {
let module = require('./child-app/app.js');
console.log(module);
$oclazyload.load({name: 'childApp'});
resolve(module.controller);
});
})
}
},
controller: function() {
}
})
}]);
Child app:
'use strict';
import childAppTemplateURL from '../templates/child-app.html';
import childAppController from './controllers/childAppController';
export default angular.module('parentApp.childApp', [])
.component('applicationListApp', {
templateUrl: childAppTemplateURL,
controller: childAppController
});
The problem was unrelated to the require.ensure implementation. It was caused by some weirdness in the way ocLazyLoad is packaged (https://github.com/ocombe/ocLazyLoad/issues/179). The fix in my case was simple, I just added 'oc.lazyLoad' to the module dependencies.
angular.module('parentApp', [
'ui.router',
'oc.lazyLoad'
])
To answer two of my own questions, Webpack does indeed make a request to the server for the bundle, and you do not have to manually load the bundle. One gotcha that really confused me: the resolve block will fail silently if it contains a promise that won't resolve. In my case $ocLazyLoad.load() was failing, but there was no indication of the failure. The only clue was that the state provider wasn't adding the <child-app></child-app> markup to the DOM, which meant that it was actually initializing the state.

Make angular Manually go to compile phase

I'm using ocLazyLoad to load my modules on demand in Angular. These modules are totally different and we should check using the server which module should we load. Before Using ocLazyLoad I was loading all concatenated js files of these separate modules in my main View. So all of them we're loading.
Now as it's obvious I'm loading these files on demand, which mean it should read data from the server and see which modules should be loaded and load a megabyte File and most importantly load it before angular begins to compile the View.
I was using ocLazyLoad's promise to $compile the view (body) but since it was being done twice or even more sometimes my directives weren't working.
So I think I have two options:
Make angular manually compile the view (which I searched a lot and I couldn't find how!!) after the modules are injected and loaded by ocLazyLoad!
Read the list of needed modules from the server and use LazyLoad in the maybe run phase of the app or even before angular.bootstrap. (I've done this already but since the $http request is being done async It works sometimes and it loads before angular compile and sometimes it doesn't)
I would appreciate it if you could help me with this.
spa.run(['$rootScope', '$location', '$route', '$localStore', '$templateCache', 'Analytics', 'preflightValue', '$ocLazyLoad', '$http',
'$compile', function($rootScope, $location, $route, $localStore, $templateCache, Analytics, preflightValue, $ocLazyLoad, $http, $compile ) {
$rootScope.$on('cfpLoadingBar:completed', function(event, args) {
angular.element($('body')).addClass('cfp-done');
});
$http({
method: 'POST',
url: url,
data: {
route: "/dashboard"
},
headers: {
// My Headers
}
}).success(function (data, status, header, config) {
var currentModules = $ocLazyLoad.getModules();
for (var i = 0; i < data.data.cloudwares.length; i++) {
var moduleName = data.data.cloudwares[i];
if (currentModules.indexOf(moduleName) === -1 && moduleName !== 'bot-manager') {
preflightValue.addModule(moduleName);
}
}
$ocLazyLoad.load(preflightValue.modulesList()).then(function () {
preflightValue.emptyList();
});
});
}]);
// Update
Please pay attention that we're not using ui-router or ngRouter. We're getting the state of our route from the server each time using $resource.
In your config file try to state the route with lazyload event
app
.config(function ($stateProvider, $urlRouterProvider,ParseProvider){
.state ('home', {
url: '/home',
// templateUrl: 'views/home.html',
templateUrl: 'views/home.html',
resolve: {
loadPlugin: function($ocLazyLoad) {
return $ocLazyLoad.load ([
{
name: 'css',
insertBefore: '#app-level',
files: [
'vendors/bower_components/fullcalendar/dist/fullcalendar.min.css',
],
},
{
name: 'vendors',
insertBefore: '#app-level-js',
files: [
'vendors/sparklines/jquery.sparkline.min.js',
'vendors/bower_components/jquery.easy-pie-chart/dist/jquery.easypiechart.min.js',
'vendors/bower_components/simpleWeather/jquery.simpleWeather.min.js'
]
}
])
}
}
})
})
from this you can load any scripts to your view.
I Actually Solved this Issue by asking another question! All I had to do was to return two promises!
Find the solution Here: Use a $routeProvider resolve function inside another one

Best practice for including scripts in Angular templates?

I have an Angular SPA with several tabs, which are served by templates. Each of these tabs requires different scripts (both local and CDN), so I'd only like to load scripts when needed, ie I will need to include them inside the templates (or associated controllers) themselves.
So far, I've tried this approach:
// Start template
<data-ng-include src="http://maps.googleapis.com/maps/api/js"></data-ng-include>
// Rest of stuff
// End template
This doesn't seem to be working. Yes, I have jQuery included in the header. What is the best way to go about this? Seems like it should be much simpler to include scripts inside templates.
You can use any of the lazy loader (on demand loader) library like requireJS, ocLazyLoad.
I have successfully implemented ocLazyLoad in one of our enterprise application, because it provide lots of usefull features if you are developing modular Single Page Application:
Easy to implement. You can lazy load any resource anywhere inside your application
Dependencies are automatically loaded
Debugger friendly (no eval code)
Can load any client side resouce like js/css/json/html
Compatible with AngularJS 1.2.x/1.3.x/1.4.x
You can also load any resource inside service, factory, directive also.See example
Resolve any resource before executing any state if you are using ui.router library for routing.See example
You can also lazy load resource in .config() of any module.See example
It also gives you the functionalty of logging module loading events.
All references are taken from official website of ocLazyLoad
If you want to include JS. I would tell you , please use :
cLazyLoad JS
Here is example:
var myapp = angular.module('myApp', ['oc.lazyLoad']);
myApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
template: '<div ui-view class="ngslide"></div>',
resolve: {
deps: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load([js/jquery.main.min.js','js/ie10-viewport-bug-workaround.js']);
}]
}
})
});
I use a simple directive for this (here :
interface AddScriptDirectiveAttributes extends IAttributes {
type: string;
src: string;
}
export function addScript(): IDirective {
return {
restrict: 'E',
link: function (scope: IScope, element: JQuery, attrs: AddScriptDirectiveAttributes) {
let script = document.createElement('script');
if (attrs.type) {
script.type = attrs.type;
}
script.src = attrs.src;
document.body.appendChild(script);
}
};
}
Usage would be something like this:
<add-script src="script" type="application/javascript"></add-script>

Angular controller not loading using OcLazyLoad and ngRoute

I have an app that which has a load of scripts loading initially and the list is growing as development goes on. I want to lazy load scripts which contain controllers as and when needed. I am using OcLazyLoad along with ngRoute for the routing option (i did try ui-route which actually had the same end result but was causing other app issues). The lazy load and routing is working fine, scripts and views are only loaded when needed, but the issue is the controller is not being loaded (Argument 'caseReviewController' is not) so it's as though the controller does not exist.
Here is a simple version of what I have in my app.js file...
var app = angular.module('dashboard', ['oc.lazyLoad', 'ngRoute', 'LocalStorageModule']);
app.config(function ($ocLazyLoadProvider, $routeProvider, localStorageServiceProvider) {
$ocLazyLoadProvider.config({
loadedModules: ['dashboard'], modules: [
{
name: 'caseReview',
files: ['js/controllers/case-review.js']
}
]
});
$routeProvider
//other routes here...
.when('/case-review', {
templateUrl: 'views/case-review.html',
controller: 'caseReviewController',
resolve: {
loadModule: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load('caseReview');
}]
}
})
});
In the seperate case-review.js file I have a simple controller
app.controller('caseReviewController', function($scope, localStorageService, $route){
//do stuff
});
This controller is not being found or executed but the view and js file are being lazy loaded as expected. Any help would be great.
Thanks.
In your separate case-review.js, you must get the reference of app.
angular.module('dashboard').controller('caseReviewController', function($scope, localStorageService, $route){
//do stuff
});
As you've mentioned it's in separate file, it may not know about the app variable. It's better to get the reference of the angular module in the separate file.
This must solve your issue.

Categories