As your single page application expands, the download time is also getting longer. This is not good for improving the user experience (hint: But the user experience is the reason why we develop a single page application). More code means larger files, until code compression doesn't meet your needs, and the only thing you can do for your users is not to let him download the entire application at once. At this point, deferred loading comes in handy. Instead of downloading all the files at once, let the user download only the files he needs now.
So. How do I get your application to implement deferred loading? It's basically divided into two things. Split your module into small chunks and implement mechanisms that allow these blocks to be loaded on demand. Sounds like a lot of work, doesn't it? If you use Webpack, that's not true. It supports the Out-of-the-box code splitting feature. In this article I assume you are familiar with Webpack, but if you do not, here is an introduction. To make a long story short, we will also use Angularui Router and oclazyload.
The code can be on the GitHub. You can fork it anytime.
Configuration of Webpack
Nothing special, really. In fact, the only difference is that you can copy and paste directly from the document, using Ng-annotate to keep our code simple and Babel to use some of the magic features of ECMAScript 2015. If you are interested in ES6, you can take a look at this previous post. Although these things are great, they are not what is necessary to implement deferred loading.
Webpack.config.js
var config = {
entry: {
app: ['./src/core/bootstrap.js '],
},
output: {
Path: __dirname + '/build/',
filename: ' Bundle.js ',
},
resolve: {
root: __dirname + '/src/',
},< C12/>module: {
noparse: [],
loaders: [
{test:/\.js$/, exclude:/node_modules/,
loader: ' Ng-annotate!babel '},
{test:/\.html$/, Loader: ' Raw '},
]
}}
;
module.exports = config;
Application
The application module is the main file, and it must be included in the Bundle.js, which is required to be forced to download on every page. As you can see, we don't load anything complicated, except for the global dependencies. Unlike the load controller, we only load the routing configuration.
App.js
' use strict ';
Export default require (' angular ')
. Module (' Lazyapp ', [
require (' Angular-ui-router '),
require (' Oclazyload '),
require ('./pages/home/home.routing '). Name,
require ('./pages/messages/messages.routing ') ). Name,
]);
Routing configuration
All deferred loading is implemented in the routing configuration. As I said, we're using Angularui Router because we need to implement nested views. We have several use cases. We can load the entire module (including the sub-state controller) or every state to load a controller (regardless of the dependency on the parent states).
Load the entire module
When the user enters the/home path, the browser downloads the home module. It includes two controllers for the home and Home.about two state. We can implement deferred loading through the Resolve property in the state's configuration object. Thanks to Webpack's require.ensure approach, we can create the first block of code from the home module. It's called 1.bundle.js. If there is no $ocLazyLoad. Load, we will find an error Argument ' HomeController ' is not a function, got undefined, because in the design of the angular, it is loaded after the application is started Part of the way is not feasible. But $ocLazyLoad. Load allows us to register a module in the startup phase and then use it after it has been loaded.
Home.routing.js
' use strict ';
function homerouting ($urlRouterProvider, $stateProvider) {
$urlRouterProvider. Otherwise ('/home ');
$stateProvider
. State (' home ', {
URL: '/home ',
template:require ('./views/home.html '),
Controller: ' HomeController as VM ',
resolve: {
Loadhomecontroller: ($q, $ocLazyLoad) => {return
$q (resolve) = > {
require.ensure ([], () => {
//Load whole module let
module = require ('./home ');
$ocLazyLoad. Load ({name: ' Home '});
Resolve (Module.controller);
});
});}}
. State (' Home.about ', {
URL: '/about ',
template:require ('./views/home.about.html '),
controller: ' Homeaboutcontroller as VM ',
} ')
;
Export default angular
. Module (' home.routing ', [])
. config (homerouting);
The controller is treated as a dependency of the module.
Home.js
' use strict ';
Export default angular
. Module (' Home ', [
require ('./controllers/home.controller '). Name,
require ('./ Controllers/home.about.controller '). Name
]);
Load controller only
What we do is the first step forward, then we proceed to the next step. This time, there will be no big modules, only a streamlined controller.
//Messages.routing.js ' use strict '; function messagesrouting ($stateProvider) {$statePro Vider. State (' messages ', {url: '/messages ', Template:require ('./views/messages.html '), controller: ' Messagescontroller as VM ', resolve: {loadmessagescontroller: ($q, $ocLazyLoad) => {return $q (resolve) => {Requir E.ensure ([], () => {//Load only Controller module let module = require ('./controllers/messages.controller '); $ocLazyL
Oad.load ({name:module.name});
Resolve (Module.controller);
})
}); }}). State (' Messages.all ', {url: '/all ', Template:require ('./views/messages.all.html '), controller: '
Messagesallcontroller as VM ', resolve: {loadmessagesallcontroller: ($q, $ocLazyLoad) => {return $q (resolve) => { Require.ensure ([], () => {//Load only Controller module let module = require ('./controllers/messages.all.controller ')
;
$ocLazyLoad. Load ({name:module.name});
Resolve (Module.controller);
})
}); }
}
})
I believe there is nothing special here, and the rules can stay the same.
Load view (views)
Now let's take a moment to release the controller and focus on the view. As you may have noticed, we embed the view into the routing configuration. This would not be a problem if we didn't put all of the routing configuration into bundle.js, but we need to do that now. This case is not a delay in loading the routing configuration but a view, so it is very simple when we use Webpack to implement it.
Messages.routing.js .....
State (' Messages.new ', {
URL: '/new ',
templateprovider: ($q) => {return
$q (resolve) => {
//lazy Load the view
require.ensure ([], () => Resolve (Require ('./views/messages.new.html '));})
;
controller: ' Messagesnewcontroller as VM ',
resolve: {
Loadmessagesnewcontroller: ($q, $ocLazyLoad) = > {return
$q ((Resolve) => {
require.ensure ([], () => {
//Load only controller module let
mo Dule = require ('./controllers/messages.new.controller ');
$ocLazyLoad. Load ({name:module.name});
Resolve (Module.controller);
})
}
);} Export default angular
. Module (' messages.routing ', [])
. config (messagesrouting);
Beware of repetitive dependencies
Let's look at the contents of Messages.all.controller and Messages.new.controller.
Messages.all.controller.js
' use strict ';
Class Messagesallcontroller {
Constructor (msgstore) {
this.msgs = Msgstore.all ();
}
}
Export default angular
. Module (' Messages.all.controller ', [
require (' Commons/msg-store '). Name,
])
. Controller (' Messagesallcontroller ', messagesallcontroller);
Messages.all.controller.js
' use strict ';
Class Messagesnewcontroller {
Constructor (msgstore) {
this.text = ';
This._msgstore = Msgstore;
}
Create () {
this._msgstore.add (this.text);
This.text = ';
}
}
Export default angular
. Module (' Messages.new.controller ', [
require (' Commons/msg-store '). Name,
])
. Controller (' Messagesnewcontroller ', messagesnewcontroller);
The root of our problem is require (' Commons/msg-store '). Name. It needs to msgstore this service to implement message sharing between controllers. This service is present in two packages. There is one in the Messages.all.controller, and one in the Messages.new.controller. Now, it has no room for optimization. How to solve it? Only need to add Msgstore as application module dependencies. Although this is not perfect, in most cases, this is enough.
App.js
' use strict ';
Export default require (' angular ')
. Module (' Lazyapp ', [
require (' Angular-ui-router '),
require (' Oclazyload '),
///Msgstore as Global dependency
require (' Commons/msg-store '). Name,
require ('./pages/ Home/home.routing '). Name,
require ('./pages/messages/messages.routing '). Name,
]);
Tips for unit Testing
Changing the Msgstore to a global dependency does not mean that you should remove it from the controller. If you do this, when you write the test, if you do not simulate this dependency, then it will not work properly. Because in a unit test, you only load this one controller, not the entire application module.
Messages.all.controller.spec.js
' use strict ';
Describe (' Messagesallcontroller ', () => {
var controller,
Msgstoremock;
Beforeeach (Angular.mock.module) (Require ('./messages.all.controller '). Name);
Beforeeach (Inject ($controller) => {
Msgstoremock = require (' Commons/msg-store/msg-store.service.mock ');
Spyon (Msgstoremock, ' all '). And.returnvalue ([' foo ',]);
Controller = $controller (' Messagesallcontroller ', {msgstore:msgstoremock});
});
It (' Saves Msgstore.all () in Msgs ', () => {
expect (Msgstoremock.all). tohavebeencalled ();
Expect (CONTROLLER.MSGS). toequal ([' foo ',]);
});
The above content is small to share the webpack to achieve ANGULARJS delay loading, I hope to help you!