下面有两个问题:
1.当application启动完成之后,针对一个module如何进行延迟文件的加载?
2.在application中,代替你选择的script加载器,应该在哪里进行实际的加载?
问题1造成的原因是,在application启动以后,使用module API无法进行文件的注册。也就是说,如果你想在启动后的app中创建一个新的controller,如下:
angular.module('app').controller('SomeLazyController', function($scope)
{
$scope.key = '...';
});
那么当你使用ng-controller标签关联这个controller时,会产生下面的异常:
Error: Argument ‘SomeLazyController’ is not a function, got undefined
目前,据我所了解,在启动后的application中注册文件,唯一的方法不是使用module API,而是使用AngularJs的第三方服务。
第三方服务是由一些对象组成,这些对象用来创建和配置AngularJs文件的实例。因此,我们用$controllerProvider服务来进行controller的延迟注册。以此类推,$compileProvider服务用来延迟注册directive,$filterProvider服务用来延迟注册filter,$provider服务用来延迟注册service。下面是关于controller和service的例子:
// Registering a controller after app bootstrap
$controllerProvider.register('SomeLazyController', function($scope)
{
$scope.key = '...';
});
// Registering a directive after app bootstrap
$compileProvider.directive('SomeLazyDirective', function()
{
return {
restrict: 'A',
templateUrl: 'templates/some-lazy-directive.html'
}
})
// etc
第三方服务仅仅在module配置期间生效。因此,他们之间一直保持着联系,用来延迟注册文件。例如,通过保持一个相关的服务,你可以像下面的例子那样建立app module:
appModule.js
(function()
{
var app = angular.module('app', []);
app.config(function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide)
{
app.controllerProvider = $controllerProvider;
app.compileProvider = $compileProvider;
app.routeProvider = $routeProvider;
app.filterProvider = $filterProvider;
app.provide = $provide;
// Register routes with the $routeProvider
});
})();
然后就可以用下面的方法延迟注册controller:
someLazyController.js
angular.module('app').controllerProvider.resgister('SomeLazyController', function($scope)
{
$scope.key = '...';
});
但问题依然存在,我们在什么地方延时加载这些controller文件,来取代使用<script>。目前,仅仅有一个地方可以完成这个工作,那就是在定义路由属性的地方。
当使用$routeProvider服务来定义路由时,你可以指定一个key/factory的依赖(页面与js文件的依赖)map,把映射注入到路由的controller中。这个map名为'resolve':
$routeProvider.when('/about', {templateUrl:'views/about.html', controller:'AboutViewController' resolve:{key:factory});
map中的'key'表示依赖的名称,而'factory'可以是一个已存在的service的别名(string),该service将作为依赖使用,也可以是一个可注入的方法(function),方法的返回值将作为依赖使用。如果这个方法返回了一个promise,该promise会在route触发之前运行。因此,这些依赖会进行异步的重新获取,就像延迟加载文件那样,我们使用依赖map中的方法会得到一个promise,一旦文件被延迟加载了,这个promise就会运行。这将确保在route被触发之前,所有的文件都会被延迟加载。下面是一个路由定义的例子,其中指定了使用$script.js加载器来进行依赖的延迟加载:
$routeProvider.when('/about', {templateUrl:'views/about.html', resolve:{deps:function($q, $rootScope)
{
var deferred = $q.defer();
var dependencies =
[
'controllers/AboutViewController.js',
'directives/some-directive.js'
];
// Load the dependencies
$script(dependencies, function()
{
// all dependencies have now been loaded by so resolve the promise
$rootScope.$apply(function()
{
deferred.resolve();
});
});
return deferred.promise;
}}});
需要注意的是,在上面的例子中,promise的运行过程在$scriptjs的上下文之中,却不在AngularJs的上下文之中,所以在promise运行时要通知AngularJs。在$rootScope中的$apply方法中执行promise,可以实现对AngularJs的通知:
$rootScope.$apply(function()
{
deferred.resolve();
});
如果不在$rootScope中的$apply方法中执行promise,route将不会在初始化page时触发。
现在应用以上的内容来定义app module,如下:
appModule.js
(function()
{
var app = angular.module('app', []);
app.config(function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide)
{
app.controllerProvider = $controllerProvider;
app.compileProvider = $compileProvider;
app.routeProvider = $routeProvider;
app.filterProvider = $filterProvider;
app.provide = $provide;
// Register routes with the $routeProvider
$routeProvider.when('/', {templateUrl:'views/home.html'});
$routeProvider.when('/about', {templateUrl:'views/about.html', resolve:{deps:function($q, $rootScope)
{
var deferred = $q.defer();
var dependencies =
[
'controllers/AboutViewController.js',
'directives/some-directive.js'
];
$script(dependencies, function()
{
// all dependencies have now been loaded by $script.js so resolve the promise
$rootScope.$apply(function()
{
deferred.resolve();
});
});
return deferred.promise;
}}});
});
})();
最后,你可以使用$script.js相同的方式启动app:
appBootStrap.js
// This file will be loaded from index.html
$script(['appModule.js'], function()
{
angular.bootstrap(document, ['app'])
});
这些就是在AngularJs中实现延迟加载的大概步骤了。总的来说,首先你要确定app module持有相关providers的实例。然后确保使用的是这些providers来延迟注册你所需的文件,而不要使用module API。接着在你的路由定义中,通过'resolve'方法返回一个promise,一旦路由触发,便加载延迟的文件并运行promise。这确保在路由触发之前,你的所有延迟文件都会被加载完成。千万不要忘记当作用域在AngularJs上下文之外时,在$rootScope的$apply方法中执行promise。再后,在启动app之前,你要创建'bootstrap'文本来首次加载app module。最后,将'bootstrap'文本与你的'index.html'关联起来。
原文链接:http://ify.io/lazy-loading-in-angularjs/
参考文章: