1. 程式人生 > >AngularJs中的延遲載入

AngularJs中的延遲載入

        當你使用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'關聯起來。

參考文章: