We encapsulate the implementation of dynamic loading Controller method into a general module, and name the module ngCommon.
(function (angular) {'use strict'; var CommonApp = angular.module('ngCommon'); ... })(angular);
Next, we implement a dynamic js loading method $require.
/* Record the loaded js */ var loaded = {}; /* Check whether to load */ var checkLoaded = function (url) { return !url || !angular.isString(url) || loaded[url]; }; CommonApp.factory('$require', ['$document', '$q', '$rootScope', function ($document, $q, $rootScope) { return function (url) { var script = null; var onload = null; var doc = $document[0]; var body = doc.body; var deferred = $q.defer(); if (checkLoaded(url)) { deferred.resolve(); } else { script = doc.createElement('script'); onload = function (info) { if (info === 1) { deferred.reject(); } else { loaded[url] = 1; /* AngularJS < 1.2.x Please use $timeout */ $rootScope.$evalAsync(function () { deferred.resolve(); }); } script.onload = script.onerror = null; body.removeChild(script); script = null; }; script.onload = onload; script.onerror = function () { onload(1); }; script.async = true; script.src = url; body.appendChild(script); } return deferred.promise; }; }]);
Then we focus on the dynamic loading of Controller through the resolve function of $routeProvider route.
CommonApp.provider('$routeResolver', function () { this.$get = function () { return this; }; this.route = function (routeCnf) { var controller = routeCnf.controller; var controllerUrl = routeCnf.controllerUrl; if (controllerUrl) { routeCnf.reloadOnSearch = routeCnf.reloadOnSearch || false; routeCnf.resolve = { load: ['$route', '$require', 'ControllerChecker', function ($route, $require, ControllerChecker) { var controllerName = angular.isFunction(controller) ? controller($route.current.params) : controller; var url = angular.isFunction(controllerUrl) ? controllerUrl($route.current.params) : controllerUrl; if (checkLoaded(url) || (controllerName && ControllerChecker.exists(controllerName))) { loaded[url] = true; return; } return $require(url); }] }; } return routeCnf; }; })
Look at the code above, a Controller checker is also injected to check whether the current Controller has been registered. If not, we will load the relevant js to register a new Controller. The code is as follows:
CommonApp.service('ControllerChecker', ['$controller', function ($controller) { return { exists: function (controllerName) { if (angular.isFunction(window[controllerName])) { return true; } try { $controller(controllerName, {}, true); return true; } catch (e) { return false; } } }; }]);
Finally, let's add a way to annotate dynamic volumes.
CommonApp.setupRegister = function (module) { module.config([ '$controllerProvider', '$compileProvider', '$filterProvider', '$provide', function ($controllerProvider, $compileProvider, $filterProvider, $provide) { module.register = { controller: $controllerProvider.register, directive: $compileProvider.directive, filter: $filterProvider.register, factory: $provide.factory, service: $provide.service, value: $provide.value, constant: $provide.constant }; } ]); };
This is almost finished. How to use it?
var DemoApp = angular.module('DemoApp',['ngRoute','ngCommon']); /* Call dynamic registration method to add dynamic registration method for current module */ angular.module('ngCommon').setupRegister(DemoApp); DemoApp.config(['$routeProvider', '$routeResolverProvider', function ($routeProvider, $routeResolverProvider) { var route = $routeResolverProvider.route; $routeProvider.when('/index', route({ templateUrl: './view/index.html'), controller: 'IndexController', /* It's stated here that the controller doesn't need to state ng controller in html anymore */ controllerUrl: './controller/index.js') })) .otherwise('/index'); /* ./controller/index.js */ DemoApp.register.controller('IndexController', ['$scope', '$require', function($scope, $require) { ... /* Loading a js file dynamically */ $require(url).then(function () { ... }); }]);