AngularJs中的延遲載入
下面有兩個問題:
1.當application啟動完成之後,針對一個module如何進行延遲檔案的載入?
2.在application中,代替你選擇的script載入器,應該在哪裡進行實際的載入?
問題1造成的原因是,在application啟動以後,使用module API無法進行檔案的註冊。也就是說,如果你想在啟動後的app中建立一個新的controller,如下:
angular.module('app').controller('SomeLazyController', function($scope)
{
$scope.key = '...';
});
那麼當你使用ng-controller標籤關聯這個controller時,會產生下面的異常:目前,據我所瞭解,在啟動後的application中註冊檔案,唯一的方法不是使用module API,而是使用AngularJs的第三方服務。Error: Argument ‘SomeLazyController’ is not a function, got undefined
第三方服務是由一些物件組成,這些物件用來建立和配置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
然後就可以用下面的方法延遲註冊controller:(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 }); })();
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'關聯起來。
參考文章: