当你使用AngularJs中的routes/views模式建立大型网站或者应用的时候,把所有的自定义文件,如controllers和directives等在初始化时全部加载进来,确实不是一个好的办法。最好的方式是,初始化时仅仅加载所需要的文件。这些文件可能会依赖一个连接或者多个文件,然而它们仅仅被特定的route所使用。当我们切换route时,未被加载的文件将会按需加载。这不但能提高初始化页面的速度,而且可以防止带宽浪费。这篇文章,我将展示如何进行延迟加载。
下面有两个问题:
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:///lazy-loading-in-angularjs/
参考文章:
http://www./blog/2553-loading-angularjs-components-after-your-application-has-been-bootstrapped.htm
http://www./blog/2554-loading-angularjs-components-with-requirejs-after-application-bootstrap.htm
|