1. ホーム
  2. ジャバスクリプト

AngularJsにおける遅延ロード

2022-02-26 18:55:04
        AngularJsのroutes/viewsパターンを使って大規模なウェブサイトやアプリケーションを構築する場合、コントローラやディレクティブなどのカスタムファイルをすべて初期化時に読み込むことは、実は良い方法とは言えません。最適な方法は、初期化時に必要なファイルのみをロードすることです。これらのファイルは接続に依存していたり、複数のファイルがあったりしますが、特定のルートのみで使用されます。ルートを切り替えると、ロードされていないファイルはオンデマンドでロードされます。これにより、ページの初期化速度が向上するだけでなく、帯域の浪費を防ぐことができます。この記事では、遅延ロードを行う方法を紹介します。
ここで2つの質問をします。
1. アプリケーションの起動後に、モジュールのファイル読み込みを延期するにはどうすればよいですか?
2. 選択したスクリプトローダーではなく、アプリケーションのどこで実際の読み込みを行うべきですか?

        質問1は、アプリケーション起動後にモジュールAPIを使用してファイル登録ができないことが原因です。つまり、以下のように、起動後のアプリで新しいコントローラを作成したい場合です。

angular.module('app').controller('SomeLazyController', function($scope)
{
    $scope.key = '...' ;
});

        そして、このコントローラをng-controllerタグに関連付けると、以下のような例外が発生します。
Error: Argument 'SomeLazyController' is not a function, got undefined

        現在、私が知る限り、起動後にアプリケーションにファイルを登録する方法は、モジュールAPIを使うのではなく、AngularJsのサードパーティサービスを使うしかありません。

        サードパーティサービスは、AngularJsのファイルのインスタンスを作成したり、設定したりするためのオブジェクトで構成されています。そこで、コントローラの遅延登録のために $controllerProvider サービスを使用します。類似の例として、$compileProvider サービスはディレクティブの登録遅延に、$filterProvider サービスはフィルタの登録遅延に、$provider サービスはサービスの登録遅延に使用されます。以下は、コントローラとサービスの例です。

// 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

        サードパーティーのサービスは、モジュールの設定中にのみ有効になります。そのため、互いに連絡を取り合い、ファイルの登録を遅らせるために利用されます。例えば、関連するサービスを保持しておくことで、以下の例のようにappモジュールを作成することができます。

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
    });
})();

        そして、以下のようにコントローラの登録を遅らせることができます。

someLazyController.js

angular.module('app').controllerProvider.resgister('SomeLazyController', function($scope)
{
    $scope.key = '...' ;
});

        しかし、これらのコントローラファイルの読み込みを、<script> の代わりにどこで遅延させるかという疑問が残ります。現在、これを行うことができるのは一箇所だけで、それはルートのプロパティが定義されている場所です。
ルート定義に $routeProvider サービスを使用する場合、キーとファクトリの依存関係 (ページと js ファイルの依存関係) のマップを指定して、ルートのコントローラにマッピングをインジェクトすることができます。このマップは 'resolve' という名前です。
$routeProvider.when('/about', {templateUrl:'views/about.html', controller:'AboutViewController' resolve:{key:factory});

        マップの「キー」は依存関係の名前を表し、「ファクトリー」は依存関係として使われる既存のサービスの別名(文字列)か、その戻り値が依存関係として使われる注入可能なメソッド(関数)であることができます。メソッドがプロミスを返す場合,ルートがトリガーされる前にプロミスが実行されます.したがって、これらの依存関係は、遅延ファイルロードのように、非同期で再フェッチされることになります。依存関係マップのメソッドを使用して、ファイルが遅延された時点で実行されるプロミスを取得するのです。これにより、ルートがトリガーされる前にすべてのファイルが遅延ロードされることが保証されます。以下は、依存関係の遅延ロードのために $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;
}}});

        上記の例では、プロミスは$scriptjsのコンテキストで実行されますが、AngularJsのコンテキストでは実行されないことに注意してください。プロミスが実行されたらAngularJsに通知します。$rootScopeの$applyメソッドでプロミスを実行し、AngularJs通知の通知を有効にしてください。
$rootScope.$apply(function()
{
    deferred.resolve();
});

        rootScopeの$applyメソッドでプロミスが実行されないと、ページの初期化時にルートがトリガーされません。
        さて、上記を応用してappモジュールを以下のように定義します。

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と同じ方法で、アプリを起動します。

appBootStrap.js

// This file will be loaded from index.html
$script(['appModule.js'], function()
{
    angular.bootstrap(document, ['app'])
});

        以上が、AngularJsで遅延ロードを実装するための大まかな手順です。簡単に説明すると、まずappモジュールが関連するプロバイダのインスタンスを保持していることを確認する必要があります。次に、ルート定義で 'resolve' メソッドを介してプロミスを返し、ルートがトリガーされたら、遅延されたファイルをロードし、プロミスを実行します。そして、アプリを起動する前に、アプリモジュールを初めて読み込むための「bootstrap」テキストを作成し、最後に「bootstrap」テキストを「index.html」に関連付けます。

元記事へのリンクです。 http://ify.io/lazy-loading-in-angularjs/

参考記事

http://www.bennadel.com/blog/2553-loading-angularjs-components-after-your-application-has-been-bootstrapped.htm

http://www.bennadel.com/blog/2554-loading-angularjs-components-with-requirejs-after-application-bootstrap.htm