lazy loading of components in angular with browserify - javascript

I am in process of outlining a architecture of a fairly complex application based on angular. So I started with the angular-seed project and it seems to be a good starting point. What bothers me is that angular apps by nature involves loading everything upfront. With script loaders, there doesn't seems to be a clean way around.
I came from a backbone.js background and there it was quiet straight to use the require.js for lazy loading based on the router callbacks. In angular routes are somewhat defined like the below:
// Declare app level module which depends on views, and components
angular.module('myApp', [
'ngRoute'
]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when({templateURL:'../tmpl.html',controller:'view1Ctrl'})
.when({templateURL:'../tmpl.html',controller:'view1Ctrl'})
.otherwise({redirectTo: '/view1'});
}]);
now here, $routeProvider.when({templateURL:'../tmpl.html',controller:'view1Ctrl'}) I would like to lazily load the controller and the template. I was tempted to use something like:
$routeProvider.when({templateURL:'../tmpl.html',controller:require('view1Ctrl'})
using browserify but then it doesn't seems to be clean and not even with require. I know this question has been asked several times on SO some way or the other but i haven't found a emphatic answer to this.
My preference here is to use the browserify as it supports the much loved cjs modules in browser.

I'm not sure how to do this with Browserify, as I've never tried it myself but I would strongly recommend you look into ocLazyLoad.
As a standalone service, it works wonders with loading files (json, css, js, templates - you name it) and injecting it into your already running angular application.
With that said, it works even better(imo) coupled with a router (the default angular one, or ui-router).
There are some 'seed projects' that showcase how one could do it with ocLazyLoad coupled with SystemJS.
https://github.com/Swimlane/angular-systemjs-seed
https://github.com/lookfirst/systemjs-seed
https://github.com/lookfirst/ocLazyLoad-SystemJS-Router
https://github.com/kasperlewau/ng-jspm-seed
But you don't even need that.
If you go with ui-router, ui-router-extras and ocLazyLoad you can put something like this together to lazy load states:
main.js
/**
* Inject the needed dependencies into our main module.
*/
var main = angular.module('main', [ 'ui.router', 'ct.ui.router.extras.future', 'oc.lazyLoad' ]);
/**
* Define the lazy loaded states.
*/
main.constant('lazyLoadedStates', [
{
name: 'about',
url: '/about',
type: 'lazy',
src: [
'/path/to/about.module.js',
'/path/to/AboutController.js'
]
}
]);
/**
* Setup the behaviour for when we hit a futureState with the 'lazy'
* type.
*
* 1. Setup a deferred object.
* 2. Resolve the promise when every file defined in the futureState.src has been loaded.
* 3. Return the promise.
*/
main.config(function ($futureStateProvider, lazyLoadedStates) {
$futureStateProvider.stateFactory('lazy', function ($q, $ocLazyLoad, futureState) {
var deferred = $q.defer();
$ocLazyLoad.load(futureState.src).then(function () {
deferred.resolve();
});
return deferred.promise;
});
lazyLoadedStates.forEach($futureStateProvider.futureState);
});
That's the 'framework' out of the way - now you just need to keep adding more modules, with more code, and match a real state definition with the dummy one in the lazyLoadedStates constant.
about.module.js
/**
* Setup the _real_ '/about' state in this lazy loaded file.
*/
angular.module('about', []).config(function ($stateProvider) {
$stateProvider.state('about', {
url: '/about',
controller: 'AboutController',
template: 'some_template.html'
});
});
AboutController.js
/**
* Register the AboutController in a lazy loaded file. This could be done in about.module.js aswell,
* but we'll do it here to separate stuff and showcase loading of multiple files.
*/
angular.module('about').controller('AboutController', function ($state) {
console.log('Im on a lazy loaded state!', $state.current);
});
I hope that gives you the rough idea of how to setup lazy loaded states in Angular. I have yet to find an easier way to do this, but I'm sure there are articles out there on how to couple ui-router (or the default angular router) with some other 'lazy-loader'.
Doc links:
ocLazyLoad
ui-router-extras#future

Related

Getting Error: Unkown provider: $urlMatcherFactory

Unkown provider: $urlMatcherFactory
This is the error message that Angular throws when I try to inject $urlMatcherFactory in the app config.
I have included the ui-router JS file and have been using it for last few months.
I need to do something like this:
var dateMatcher = $urlMatcherFactory.compile("/day/{start:date}");
$stateProvider.state('calendar.day', {
url: dateMatcher
});
as this example shows.
Normally code like this won't identify $urlMatcherFactory. So I tried injecting it as a dependency on the config but then I get this error.
In this Q&A Matching url with array list of words in AngularJS ui-router you can see how to use the $urlMatcherFactory. Also a link to working plunker .
In that example, the $urlMatcherFactory is used in the .run():
.run(['$urlMatcherFactory',
function($urlMatcherFactory) {
var sourceList= ['John', 'Paul', 'George', 'Ringo']
var names = sourceList.join('|');
var urlMatcher = $urlMatcherFactory.compile("/{source:(?:" + names + ")}/:id");
$stateProviderRef
.state('names', {
url: urlMatcher,
templateUrl: 'tpl.root.html',
controller: 'MyCtrl'
});
}
]);
And that would also mean, that if you are about to use it in a config phase, you should ask for
$urlMatcherFactoryProvider (see the Provider at the end)
BUT: using providers in a config phase means - we can configure them. I mean configure the provider itself. To be later evaluable (already configured) in run phase
Configuring Providers
You may be wondering why anyone would bother to set up a full-fledged provider with the provide method if factory, value, etc. are so much easier. The answer is that providers allow a lot of configuration. We've already mentioned that when you create a service via the provider (or any of the shortcuts Angular gives you), you create a new provider that defines how that service is constructed. What I didn't mention is that these providers can be injected into config sections of your application so you can interact with them!
First, Angular runs your application in two-phases--the config and run phases. The config phase, as we've seen, is where you can set up any providers as necessary. This is also where directives, controllers, filters, and the like get set up. The run phase, as you might guess, is where Angular actually compiles your DOM and starts up your app.

AngularJS and Webpack Integration

I am looking for some help with using webpack for a large AngularJS application. We are using folder structure based on feature (each feature/page has a module and they have controllers, directives). I have successfully configured webpack to get it working with Grunt, which produces one single bundle. I want to create chunks as its going to be a large app, we would like to load modules (page/feature) artifacts asynchronously.
I am going through some of the webpack example to use 'code splitting' using require([deps],fn ) syntax. However I couldn't get the chunks lazy-loaded. First of all, I don't know where exactly, I would need to import these chunks before AngularJS would route the user to next page. I am struggling to find a clear separation of responsibility.
Did someone point me to an example AngularJS application where webpack is used to load controllers/directives/filters asynchronously after each route?
Few of the links I am following:
Should I use Browserify or Webpack for lazy loading of dependancies in angular 1.x
https://github.com/petehunt/webpack-howto#9-async-loading
http://dontkry.com/posts/code/single-page-modules-with-webpack.html
Sagar Ganatra wrote a helpful blog post about code splitting.
Suprisingly code splitting isn't really supported by angular's module system. However, there is a way to achieve code splitting by saving a reference to angular's special providers during the config-phase.
[...] when Angular initializes or bootstraps the application, functions - controller, service etc,. are available on the module instance. Here, we are lazy loading the components and the functions are not available at a later point; therefore we must use the various provider functions and register these components. The providers are available only in the config method and hence we will have to store a reference of these providers in the config function when the application is initialized.
window.app.config([
'$routeProvider',
'$controllerProvider',
'$compileProvider',
'$filterProvider',
'$provide',
function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
$routeProvider.when('/login', {
templateUrl: 'components/login/partials/login.html',
resolve: {
load: ['$q', '$rootScope', function ($q, $rootScope) {
var deferred = $q.defer();
// lazy load controllers, etc.
require ([
'components/login/controllers/loginController',
'components/login/services/loginService'
], function () {
$rootScope.$apply(function () {
deferred.resolve();
});
});
return deferred.promise;
}]
}
});
//store a reference to various provider functions
window.app.components = {
controller: $controllerProvider.register,
service: $provide.service
};
}
]);
Now inside your loginController for instance you write
app.components.controller('loginController');
to define your new controller lazily.
If you want to lazy-load your templates too I recommend to use the ui-router. There you can specify a templateProvider which is basically a function to load templates async
This is a quote from
https://github.com/webpack/webpack/issues/150
webpack is a module bundler not a javascript loader. It package files from local disk and don't load files from the web (except its own chunks).
Use a javascript loader, i. e. script.js
var $script = require("scriptjs");
$script("//ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js", function() {
// ...
});

Angular $routeProvider override-able fallback routes

I'm creating my first sizable angular project, so I've opted to break out my angular application code into directories like so:
app/
calendar/
calendar.js
contacts/
contacts.js
companies/
companies.js
...
app.js
Each of these directories will include a handful of files for views (.html), directives, services, etc.
In my app.js file, I want to setup default routes that will be used maybe 80% of the time, like this:
$routeProvider
.when('/:page', {
templateUrl: function($routeParams) {
return 'app/' + $routeParams.page + '/index.html';
},
controller: 'PageController'
})
However, I want the ability to "override" that route from within my modules so I can do things like swap out the controller, do dependency injection stuff, and so on. I would like that sort of logic contained within the module itself to keep things...well...modular, so I would rather not define each module's routing logic within app.js. So for example, in my app/calendar/calendar.js file I want to say:
$routeProvider
.when('/calendar', {
templateUrl: 'app/calendar/index.html',
controller: 'CalendarIndexController',
resolve: { ... }
})
The problem is that the definition in app.js is matched against the location first, so this calendar route is never used.
To achieve this, right now I'm just including an extra file after all of my module javascript files that sets up my fallback routes last, but this seems a little clunky. Is there anyway to define the routes within app.js so that they are override-able?
You might be able to adapt this technique for dynamically loading controllers described by Dan Wahlin here http://weblogs.asp.net/dwahlin/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs combined with your default routes where the parameter controls which view to load. You could pass the same page parameter to the resolve function to control which combination of view and controller to serve up?

AngularJS - dynamic loading of external JS based on routes

I am trying to make a simple single page mobile app with multiple views and a next\back button to control each view. I am using the Angular Mobile UI library.
The basic mockup is as follows:
<html>
<head>
<link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-base.min.css">
<link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-desktop.min.css">
<script src="js/angular/angular.min.js"></script>
<script src="js/angular/angular-route.min.js"></script>
<script src="mobile-angular-ui/dist/js/mobile-angular-ui.min.js"></script>
<script src="app/app.js"></script>
<script src="app/firstController.js"></script>
<script src="app/secondController.js"></script>
<script src="app/thirdController.js"></script>
</head>
<body ng-app="demo-app">
<div ng-view></div>
<div ng-controller="nextBackController" class="navbar navbar-app navbar-absolute-bottom">
<div class="btn-group justified">
<i class="fa fa-home fa-navbar"></i>
<i class="fa fa-list fa-navbar"></i>
</div>
</div>
</body>
</html>
App.js is as follows:
var app = angular.module('demo-app', [
"ngRoute",
"mobile-angular-ui"
]);
app.config(function($routeProvider) {
$routeProvider.when('/', { controller: "firstController",
templateUrl: "views/first.html"});
$routeProvider.when('/', { controller: "secondController",
templateUrl: "views/first.html"});
$routeProvider.when('/', { controller: "thirdController",
templateUrl: "views/first.html"});
});
controllers = {};
controllers.nextBackController = function($scope, $rootScope) {
//Simple controller for the next, back buttons so we just put it in app.js
};
app.controller(controllers);
firstController.js will contain something similar to:
controllers.firstController = function($scope) {
//Do our logic here!!!
};
The problem is if you notice at the top of the HTML page I have to load all the controllers in. This is not scalable. I want each controller to be in it's own JS file and not have to statically load each one since the user may never even require that controller. Is there a way to dynamically load the actual JS file when switching routes? or can I stick a script tag at the top of my "first.html", "second.html", etc.
If I understand correctly you need to load specific scripts for each view? I am sharing this snippet from a personal project that uses ocLazyLoader a plugin that loads modules on demand.
var myApp = angular.module("myApp", [
"ui.router",
"oc.lazyLoad",
]);
then in your routing you could load dynamic JS / CSS files accordingly, in this example I am loading the UI Select plugin dependencies
myApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
// State
.state('demo', {
url: "/demo.html",
templateUrl: "views/demo.html",
data: {pageTitle: 'demo page title'},
controller: "GeneralController",
resolve: {
deps: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load([{
name: 'ui.select',
// add UI select css / js for this state
files: [
'css/ui-select/select.min.css',
'js/ui-select/select.min.js'
]
}, {
name: 'myApp',
files: [
'js/controllers/GeneralController.js'
]
}]);
}]
}
})
If you're familiar with Grunt:
https://github.com/ericclemmons/grunt-angular-templates
Use Grunt and the above build task to create one .js from all views. Include a watch task listener over all html files in a views directory. Whenever a change is made to any partial views, a "$templateCache" entry is created with all of the html in the file and a url to alias the cache. Your routes will point to the partial views in the same manner, but the html files do not need to be present. Only the .js file with the templates. The beauty of this is that it loads once and is available on the client side for the entire session. This cuts down on http calls and your traffic can be reduced to web service calls, only.
This is the example of a template from the github link, above:
angular.module('app').run(["$templateCache", function($templateCache) {
$templateCache.put("home.html",
// contents for home.html ...
);
...
$templateCache.put("src/app/templates/button.html",
// contents for button.html
);
}]);
If you're not familiar with Grunt
Check it out. It's pretty invaluable for automating builds, minification, concatenation, transpiling, etc...
http://gruntjs.com/
Unless your app is MASSIVE, you should REALLY avoid serving small js files individually. This will noticeably slow down your app, even if you were to figure out a way to lazily fetch files on an as-needed basis as you suggest in your question.
A much better way to do this (and the way used and suggested by the AngularJS team) is to have a BUILD PROCESS (you should use grunt for this) concatenate all your javascript files, and serve them as a single app.js file. This way you can maintain an organized code base with as many tiny js files as you want, but reduce script fetching to a single request.
How to Install OCLazyLoad.
1. Download ocLazyLoad.js here
It can be found in the 'dist' folder of the git repository. You can also install it with bower install oclazyload or npm install oclazyload.
2. Add the module oc.lazyLoad to your application:
var myApp = angular.module("MyApp", ["oc.lazyLoad"]);
3. Load your JS files on demand, based on routes:
myApp.controller("MyCtrl", function($ocLazyLoad){
$ocLazyLoad.load('testModule.js');
}});`
With $ocLazyLoad you can load angular modules, but if you want to load any component (controllers / services / filters / ...) without defining a new module it's entirely possible (just make sure that you define this component within an existing module).
There are multiple ways to use $ocLazyLoad to load your files, just choose the one that you prefer.
Also don't forget that if you want to get started and the docs are not enough, see the examples in the 'examples' folder!
Quick Start
RequireJS can be really useful in this case.
You can declare the dependencies of your JS files using RequireJS.
You can refer this simple tutorial.
I think the best way is use RequireJS, as mentioned here Does it make sense to use Require.js with Angular.js? It is totally allowed and it will let you reach what you are trying.
here is an example code
https://github.com/tnajdek/angular-requirejs-seed
I am not sure how it works in standard angular, however, you could use angular-ui-router:
Controllers are instantiated on an as-needed basis, when their corresponding
scopes are created, i.e. when the user manually navigates to a state via a URL,
$stateProvider will load the correct template into the view, then bind the
controller to the template's scope.
https://github.com/angular-ui/ui-router/wiki

Understanding injection dependency in app and tests in AngularJS

I have a dependency injection (understanding) problem while testing a directive (AjaxLoader displayed only when there is a pending request).
App declaration :
angular.module('app', [
'directives.ajaxLoader',
'services.httpRequestTracker',
[...]
])
Directive code :
angular.module('directives.ajaxLoader', [])
.directive('ajaxLoader', ['httpRequestTracker',
function(httpRequestTracker) {
return {
templateUrl: 'common/ajaxLoader.tpl.html',
link: function($scope) { // This function can have more parameters after $scope, $element, $attrs, $controller
$scope.hasPendingRequests = function() {
return httpRequestTracker.hasPendingRequests();
};
}
};
}
])
Test code :
describe('ajaxLoader', function() {
beforeEach(function() {
module('directives.ajaxLoader', 'common/ajaxLoader.tpl.html');
});
describe('ajaxLoader directive', function() {});
});
From there, my directive works perfectly well in the browser, but tests fails with an error like :
Error: [$injector:unpr] Unknown provider: httpRequestTrackerProvider
<- httpRequestTracker <- ajaxLoaderDirective
Ok, so I need to inject my dependency somewhere. I have two solutions :
in my directive directly :
angular.module('directives.ajaxLoader', [
'services.httpRequestTracker'
])
in my test code directly :
beforeEach(function() {
module('directives.ajaxLoader', 'common/ajaxLoader.tpl.html', 'services.httpRequestTracker');
});
Both works, but I don't understand which one is the better and why ? And why is it working in my browser from the start and fails in my test ? In both case, all my directives and trackers are injected in my main app declaration
Thanks
Loading modules
It works in your application because services.httpRequestTracker is loaded. you did that by declaring it as a dependency of the main app module (your first code snippet).
However, when you test things, you want to mock everything that is not being tested to avoid biass. In your case, what if you had a problem in services.httpRequestTracker? ajaxLoader might be fine but your tests will fail.
Mocking
To mock everything else, you have two options:
spies (e.g. this http://angular-tips.com/blog/2014/03/introduction-to-unit-test-spies/ )
use dependency injection to substitute components
To use a dependency, you have to load the module with module().
you will have to load the dependency, but this might have a mock implementation.
Dependency Injection
Dependency injection lets you decouple classes. There is a service locator to resolve dependencies by name. That is, you say attribute a of class C is of type 'animal' (a string!). The service locator in the angular core finds which component implements it. A way of determining this is by looking up the loaded modules (e.g. dependencies of the main app module).
You didn't define any of this in your testing area (but it isn't a problem!). Karma uses a file karma.conf that contains a list of files to use. You might use this file to add libraries or mocked components.
With your particular problem:
The directive depends on httpRequestTracker. If you don't inject it there, it won't work (so it's ok).
In your test, you have to load both. That's why the first time it failed and the second it worked. However, instead of loading httpRequestTracker, I'd load a mock implementation of it.

Categories

Resources