UX, data visualization and frontend engineering. Bouldering too.

Menu

Follow Blog via Email

Angular, webpack and gulp for an SPA: Part III

In Part I, we got webpack to run with gulp, watching our Angular app. In Part II, we added routing with ui-router. Here in Part III, we check out the magic of lazy loading.

What is lazy loading? Typically, with most Single Page Apps (SPAs), what the client actually receives is the index file, downloads the application script(s), and then the client-side framework and routing kick in. From that point on, whenever you navigate to a new ‘page’, the app doesn’t really request a new page — it was all downloaded from the get-go.

Well, this can be wasteful. If your user goes to the User Settings page, she doesn’t need to download app components for other pages. Wouldn’t it be nice if the user could download only what she needs for that current page, and then download other stuff as she needs them? This is called lazy loading.

Ok, so that’s a lot of new stuff. Let’s take a look at each in turns. Up first, notice we inject the Angular module ocLazyLoad. See, Angular wants to know ALL modules (emphasis: modules. Not controllers or services, but whole modules) right off the bat and if you try to declare and inject a new module after the run block, Angular will throw a tantrum.

Next, we add a resolve statement. This guarantees the route will be resolved only when the promise inside returns:

resolve: ['$q', '$ocLazyLoad', function($q, $ocLazyLoad) {
var deferred = $q.defer();
// This is webpack's magic. When webpack sees this require.ensure block,
// it automatically creates a chunk file
// consisting of only that module and its dependencies,
// and it automatically takes care of loading it for you when you need it
require.ensure([], function() {
var mod = require('./captain-list');
// after the chunk above loads, we tell ocLazyLoad to inject the module into our Angular app
$ocLazyLoad.load({
name: 'cl3.captainList',
});
// you can resolve it with anything
deferred.resolve(mod.controller);
});
return deferred.promise;
}]

I want to emphasize again just how cool it is — whenever webpack sees a require.ensure block, it will automatically create a separate file, e.g. chunk1.bundle.js, and then load it automatically when you need it. Isn’t that mindblowingly awesome??!

One gotcha — if you do use this method, know that ng-cache loader will no longer work with your lazy-loaded components because the loader kicks in on the run block of the app. Instead, you’ll have to require in your templates using the regular html loader inside your modules:

require('html!./whatever-my-component-is');

You can experiment around with different combinations, for example, instead of using templateProvider, you could decide to bundle all your templates into the main “common” bundle to save on extra HTTP requests, e.g.:

template: require('html!./captain-list/captainList.html')

Know that there are tradeoffs, and lazy loading might not necessarily be performance-boosting. If you lazy load your modules, your users have to wait longer between page navigation. The more HTTP requests you make, the more you load your server and incur latency.

It depends on your app, your server, your users, etc. In my case, I found that, with a 310kb (total, including vendor files) bundle, perceived performance was much greater when I did not lazy load. That probably will change as app size grows.