Lazy Angular Modules
Startup time is crucial for a proper user experience and that’s where many JavaScript SPA really fail. Even though you might (and definitely should) apply code minification and bundling mechanisms, you wouldn’t want to load your entire application right upfront. It simply isn’t useful nor required. Unfortunately, as of now Angular doesn’t natively support any kind of lazy loading approach. Thus, many devs started to create custom implementations and workarounds to circumvent such lack. This article aims to explore those implementations as well as to combine them with another interesting and hotly-debated topic: proper modularization.
Feature based vs MVC based organization
Not only Angular but other frameworks as well propose the typical MVC kind of structuring and organization of your code, having a
controller
foldermodel
folderview
folder- …
While this might seem to be a clean and well organized structure initially, you soon discover that it isn’t quite as useful as it could be during development nor maintenance. When did you last open your project and say “let’s add a controller”?? Instead, you normally implement or modify some feature.
Having a MVC structure implies a continuous, tedious searching for the corresponding controller, the model and view that belong to the feature in these three separate folders.
Thus, having a “feature-based” organization and grouping of your code tends to be more useful and maintainable in the long run. Unfortunately, many Angular examples, even official ones, promote the MVC based folder structure (as of now). Take the angular-seed project or the official Yeoman generator.
That seems to change, though. The latest official Angular best practices guide on GitHub proposes to “group your code into related bundles” and references the Angular-app demo which is the code example that accompanies the Mastering Web Application Development with AngularJS book (must read!).
What you read and hear across the community might not always be as consistent as you’d like to have it. Anyway, my approach clearly follows the feature-based cut as it has already proven to be fruitful when developing JavaScript SPAs with JavaScriptMVC/CanJS as well as on a desktop application developed with PRISM.net.
The projects to take a look at for a proper feature-based organization is the source for the Angular book: GitHub link. The image below illustrates this further.
Furthermore John Papa held a session at this year’s TechEd about creating modular Angular applications with the .Net stack. While the backend is less interesting for this article, in his frontend he nicely follows such feature based organization.
Implementing a feature based approach in Angular
Angular already disposes of a “module system” which provides some sort of namespacing. That system naturally fits in the feature-based approach, so instead of having modules like
app.controllers
,app.services
you haveapp.users
,app.users.edit
, …
Furthermore, John Papa structures each module to reside in its own folder..
..and to contain a dedicated file that just contains the Angular module’s definition and eventual dependencies loading: <modulename>.module.js
.
(function () {
'use strict';
angular.module('app.dashboard', []);
})();
The official angular demo follows a nearly identical approach, but instead of having a file named <modulename>.module.js
it defines it like <modulename>.js
.
Internally that file specifies the module name and dependencies:
angular.module('admin', ['admin-projects', 'admin-users']);
The approach taken by both sounds reasonable and is what I ended up mirroring with RequireJS (as we’ll see later). The only variable here is the name of the angular module which gets repeated as hardcoded strings among submodules. Related to that topic I found an interesting article by Avi Haiat about AngularJS and RequireJS for Very Large Applications. He follows an approach where each module has at least the following files:
namespace.js
- defines the module’s name as an AMD module, taking the root namespace (../namespace
) and combines it with the current module’s name.
define([
'../namespace'
], function(rootNamespace) {
'use strict';
return rootNamespace + '.moduleone';
});
module.js
- is the actual definition of the Angular module, importing the defined namespace.
define([
'angular',
'./namespace'
], function(angular, namespace){
'use strict';
// module definition
return angular.module(namespace, []);
});
module.require.js
- is used for collecting all of the files needed for RequireJS to properly load the module. It basically bundles all of the module’s files s.t. any user has just to “require” the this file to get everything.
define([
'./module',
..,
], function(){
});
In this way you can change the module names at a single point without having to edit multiple files with hardcoded strings. Although it sounds great from a theoretical perspective, I’m not yet sure about whether the overhead of having additional files just for carrying the namespace pays off. We’ll see…
State of the Art of Lazy Loading in JavaScript
Currently RequireJS is definitely the state of the art in asynchronously (and lazy) loading of JavaScript files. It is based upon the AMD (Asynchronous Module Definition) API.
The basic usage consists of defining an AMD module, say myModule.js
define(['jquery', './logger.js'], function($, logger){
// do something
return {
doSomething: function(){
logger.log('Hi');
}
}
});
…which you can then “require” somewhere else.
require(['./myModule.js'], function(myModule){
// do something interesting with it
});
The asynch and potentially even parallel loading of the necessary files is completely managed by RequireJS. Obviously you can also programmatically “require” some further resources inside your code dynamically:
function someFunction(param){
if(param > 40){
require(['./myModule.js'], function(){
// this callback will be invoked once the dependency has been loaded.
});
}
}
This turns out to be quite useful for our lazy Angular loading implementation.
Lazy loading and dependency injection
But, Angular has already it’s “dependency injection” mechanism. Why would I want to also use RequireJS? The two have different targets: RequireJS loads your physical script files into the browser while Angular’s dependency injection mechanism loads a logical, runtime artifact by its name.
In order to have Angular’s DI mechanism work properly, all of the JavaScript code has to be loaded and known by the framework. Angular analyzes the code for definitions of controllers, services, directives etc. and injects them at other points when requested. For instance, you define your service:
// definition of a service on an existing Angular module
myModule.factory('consolelogger', function(){
return function(msg){
console.log(msg)
}
});
Then, somewhere else you specify your consolelogger
as dependency.
myModule.controller('MyController', ['consolelogger', function(consolelogger){
consolelogger('hi there');
}]);
In Angular's DI you don't have to specify the file location, but simply the name with which you defined your artifact!
Thus, Angular’s DI is good for testing and better modularity while RequireJS (or alternatives like $scriptjs and so on) is definitely the tool for lazy-on-demand loading. But attention, we cannot simply lazy-load some JavaScript files with RequireJS after our Angular app has started because the DI container simply wouldn’t recognize those newly created artifacts. Instead, we need to manually tell Angular about those new files that arrived. More on that later.
Angular and RequireJS for Lazy loading
Existing approaches for lazy loading
When I researched for a proper appraoch on implementing lazy loading in Angular, (after googling a bit) I commented on the AngularJS Googel Group. The comments from others, revealed mostly the resources I already found on the web:
- Ifeanyi Isitor on lazy loading with
$scriptjs
. He also added an implementation using RequireJS. - Bennadel on his approach to using RequireJS for lazy loading.
- angularAMD - a project that combines AngularJS+RequireJS in a library for better reusability.