状態変化時のAngularコンポーネントスクリプトの遅延読み込み

市民奴隷

この問題は、最終日かそこらで取り上げられています。

AngularJSアプリケーションに、各状態のコンポーネントのスクリプトファイルを遅延ロードさせようとしています。私はAngularで作業中の大規模なプロジェクトに取り組んでおり、index.htmlファイルは<script>さまざまなコントローラー、サービス、ライブラリのJSを含む100を超えるタグに変形しています。それらのほとんどは小さいので、ロード時間が大きな問題になるほどで​​はありませんが(それは可能ですが)、私には決してきれいに見えませんでした。

おそらく、PHPのオートローダーに慣れているか、コンパイル時に独自の依存関係をロードできるすべての言語に甘やかされているためです。アプリケーションのルートドキュメントにあるマイナーなフリンジ状態のディレクティブのスクリプトをロードする必要があること、またはそのディレクティブが実際に属するモジュールのスクリプトをロードして<script>、栄光リストなしで別のアプリケーションに移動した場合にスクリプト自体をロードしないことは、モジュール式ではありません。

いずれにせよ、私は新しいプロジェクトを開始していて、それをよりクリーンに保ちたいと思っていますが、この方法でコンポーネントをAngularにロードするには、いくつかの課題があります。それらの多くは、ドキュメントやブログ投稿、SOの質問などで一度に対処されていますが、他のAngularコンポーネントと完全に統合されるエンドツーエンドのソリューションはまだ見ていません。

  1. Angularng-appは、ページのレンダリング時にAngularとモジュールがすでにロードされている場合にのみ、ディレクティブをブートストラップします。遅延読み込みでアプリケーションを開始する場合でも、回避策が必要です。
  2. モジュールAPIのメソッドは、アプリケーションがブートストラップされる前にのみ機能します。アプリケーションがブートストラップされた後、ただしそれらを定義するスクリプトが実際にロードされた後(および実際に必要な場合)に新しいコントローラー、ディレクティブ、フィルター、またはサービスを登録するには、回避策が必要です。
  3. 遅延読み込みスクリプトとAJAXベースのサービスの呼び出しはどちらもコールバックの呼び出しを必要とし、サービス呼び出しの結果を状態コントローラーに挿入するには、状態遷移の開始時に呼び出されるサービスが実際に存在する必要があります。実際に遅延ロードされたサービスを呼び出し、状態が変化する前にそれを解決するには...回避策が必要です。
  4. これらはすべて、ぎこちなく見えないように組み合わせる必要があり、毎回車輪の再発明をしなくても、複数のアプリケーションで簡単に再利用できます。

#1と#2の答えを見てきました。明らかに、ディレクティブangular.bootstrapなしでページ全体がロードされた後にモジュールを起動するために使用できますng-appブートストラップ後にコンポーネントを追加することは少しわかりにくいですが、さまざまな$providerサービスへの参照を構成ブロックに保存すると、moduleAPIがよりシームレスに上書きされるためうまくいきます#3を解決し、それをすべて#4を満たす方法で実行することは、もう少しわかりにくいものでした。

#2を解決する上記の例は、コントローラーとディレクティブ用です。サービスの追加は、もう少し複雑で非同期のサービスであり、遅延ロードされ、特に遅延ロードされたコントローラーにデータを提供することを目的としています。Isitor氏に関しては、彼のコードは確かに概念実証としてコントローラーを登録するために機能しますが、スクリプトの遅延読み込みが理にかなっている種類のアプリケーションに簡単にスケールアップできるようにコードが記述されていません。数十から数百のインクルード、依存関係、非同期サービスを備えたはるかに大規模なアプリケーション。

私が思いついた解決策を投稿しますが、誰かがそれを改善するための提案を持っているか、劇的かつ根本的に異なる、より良い方法をすでに見つけた場合は、それを自由に追加してください。

市民奴隷

モジュールlazyに応じて、Angularモジュールのコードを次に示しui.routerます。モジュールの依存関係に含まれている場合、状態のスクリプトの遅延読み込み機能が有効になります。プライマリアプリモジュール、いくつかのレイジーコンポーネント、およびindex.htmlデモ用にサニタイズされた私の例を含めました私はScript.js実際にスクリプトのロードを処理するためにライブラリを使用しています。

angle-ui-router-lazy.js

/**
 * Defines an AngularJS module 'lazy' which depends on and extends the ui-router
 * module to lazy-load scripts specified in the 'scripts' attribute of a state
 * definition object.  This is accomplished by registering a $stateChangeStart
 * event listener with the $rootScope, interrupting the associated state change
 * to invoke the included $scriptService which returns a promise that restarts the
 * previous state transition upon resolution.  The promise resolves when the
 * extended Script.js script loader finishes loading and inserting a new <script>
 * tag into the DOM.
 *
 * Modules using 'lazy' to lazy-load controllers and services should call lazy.makeLazy
 * on themselves to update the module API to inject references for the various $providers 
 * as the original methods are only useful before bootstrapping, during configuration,
 * when references to the $providers are in scope.  lazy.makeLazy will overwrite the
 * module.config functions to save these references so they are available at runtime,
 * after module bootstrapping.
 * See http://ify.io/lazy-loading-in-angularjs/ for additional details on this concept
 *
 * Calls to $stateProvider.state should include a 'scripts' property in the object
 * parameter containing an object with properties 'controllers', 'directives', 'services',
 * 'factories', and 'js', each containing an array of URLs to JS files defining these
 * component types, with other miscelleneous scripts described in the 'js' array.
 * These scripts will all be loaded in parallel and executed in an undefined order
 * when a state transition to the specified state is started.  All scripts will have
 * been loaded and executed before the 'resolve' property's promises are deferred,
 * meaning services described in 'scripts' can be injected into functions in 'resolve'.
 */

 (function() {
    // Instantiate the module, include the ui.router module for state functionality
    var lazy = angular.module('lazy',['ui.router']);

    /**
     * Hacking Angular to save references to $providers during module configuration.
     * 
     * The $providers are necessary to register components, but they use a private injector
     * only available during bootstrap when running config blocks.  The methods attached to the
     * Vanilla AngularJS modules rely on the same config queue, they don't actually run after the
     * module is bootstrapped or save any references to the providers in this injector.
     * In makeLazy, these methods are overwritten with methods referencing the dependencies
     * injected at configuration through their run context.  This allows them to access the
     * $providers and run the appropriate methods on demand even after the module has been
     * bootstrapped and the $providers injector and its references are no longer available.
     *
     * @param module      An AngularJS module resulting from an angular.module call.
     * @returns module    The same module with the provider convenience methods updated
     * to include the DI $provider references in their run context and to execute the $provider
     * call immediately rather than adding calls to a queue that will never again be invoked.
     */
    lazy.makeLazy = function(module) {
      // The providers can be injected into 'config' function blocks, so define a new one
      module.config(function($compileProvider,$filterProvider,$controllerProvider,$provide) {
        /**
         * Factory method for generating functions to call the appropriate $provider's
         * registration function, registering a provider under a given name.
         * 
         * @param registrationMethod    $provider registration method to call
         * @returns function            A function(name,constructor) calling
         * registationMethod(name,constructor) with those parameters and returning the module.
         */
        var register = function(registrationMethod) {
          /**
           * Function calls registrationMethod against its parameters and returns the module.
           * Analogous to the original module.config methods but with the DI references already saved.
           *
           * @param name          Name of the provider to register
           * @param constructor   Constructor for the provider
           * @returns module      The AngularJS module owning the providers
           */
          return function(name,constructor) {
            // Register the provider
            registrationMethod(name,constructor);
            // Return the module
            return module;
          };
        };

        // Overwrite the old methods with DI referencing methods from the factory
        // @TODO: Should probably derive a LazyModule from a module prototype and return
        // that for the sake of not overwriting native AngularJS code, but the old methods
        // don't work after `bootstrap` so they're not necessary anymore anyway.
        module.directive = register($compileProvider.directive);
        module.filter = register($filterProvider.register);
        module.controller = register($controllerProvider.register);
        module.provider = register($provide.provider);
        module.service = register($provide.service);
        module.factory = register($provide.factory);
        module.value = register($provide.value);
        module.constant = register($provide.constant);
      });
      // Return the module
      return module;
    };

    /**
     * Define the lazy module's star $scriptService with methods for invoking
     * the extended Script.js script loader to load scripts by URL and return
     * promises to do so.  Promises require the $q service to be injected, and
     * promise resolutions will take place in the Script.js rather than Angular
     * scope, so $rootScope must be injected to $apply the promise resolution
     * to Angular's $digest cycles.
     */
    lazy.service('$scriptService',function($q,$rootScope) {
      /**
       * Loads a batch of scripts and returns a promise which will be resolved
       * when Script.js has finished loading them.
       *
       * @param url   A string URL to a single script or an array of string URLs
       * @returns promise   A promise which will be resolved by Script.js
       */
      this.load = function(url) {
        // Instantiate the promise
        var deferred = $q.defer();
        // Resolve and bail immediately if url === null
        if (url === null) { deferred.resolve(); return deferred.promise; }
        // Load the scripts
        $script(url,function() {
          // Resolve the promise on callback
          $rootScope.$apply(function() { deferred.resolve(); });
        });
        // Promise that the URLs will be loaded
        return deferred.promise;
      };

      /**
       * Convenience method for loading the scripts specified by a 'lazy'
       * ui-router state's 'scripts' property object.  Promises that all
       * scripts will be loaded.
       *
       * @param scripts   Object containing properties 'controllers', 'directives',
       * 'services', 'factories', and 'js', each containing an array of URLs to JS
       * files defining those components, with miscelleneous scripts in the 'js' array.
       * any of these properties can be left off of the object safely, but scripts
       * specified in any other object property will not be loaded.
       * @returns promise   A promise that all scripts will be loaded
       */
      this.loadState = function(scripts) {
        // If no scripts are given, instantiate, resolve, and return an easy promise
        if (scripts === null) { var d = $q.defer; d.resolve(); return d; }
        // Promise that all these promises will resolve
        return $q.all([
          this.load(scripts['directives'] || null),
          this.load(scripts['controllers'] || null),
          this.load(scripts['services'] || null),
          this.load(scripts['factories'] || null),

          this.load(scripts['js'] || null)
        ]);
      };
    });

    // Declare a run block for the module accessing $rootScope, $scriptService, and $state
    lazy.run(function($rootScope,$scriptService,$state) {
      // Register a $stateChangeStart event listener on $rootScope, get a script loader
      // for the $rootScope, $scriptService, and $state service.
      $rootScope.$on('$stateChangeStart',scriptLoaderFactory($scriptService,$state));
    });

    /**
     * Returns a two-state function for handing $stateChangeStart events.
     * In the first state, the handler will interrupt the event, preventing
     * the state transition, and invoke $scriptService.loadState on the object
     * stored in the state definition's 'script' property.  Upon the resolution
     * of the loadState call, the handler restarts a $stateChangeStart event
     * by invoking the same transition.  When the handler is called to handle
     * this second event for the original state transition, the handler is in its
     * second state which allows the event to continue and the state transition
     * to happen using the ui-router module's default functionality.
     *
     * @param $scriptService    Injected $scriptService instance for lazy-loading.
     * @param $state            Injected $state service instance for state transitions.
     */
    var scriptLoaderFactory = function($scriptService,$state) {
      // Initialize handler state
      var pending = false;
      // Return the defined handler
      return function(event,toState,toParams,fromState,fromParams) {
        // Check handler state, and change state
        if (pending = !pending) {   // If pending === false state
          // Interrupt state transition
          event.preventDefault();
          // Invoke $scriptService to load state's scripts
          $scriptService.loadState(toState.scripts)
            // When scripts are loaded, restart the same state transition
            .then(function() { $state.go(toState,toParams); });
        } else {  // If pending === true state
          // NOOP, 'ui-router' default event handlers take over
        }
      };
    };
  })();

/** End 'lazy' module */

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Lazy App</title>
    <script type='text/javascript' src='libs/script.js'></script>
    <script type='text/javascript'>
      $script.queue(null,'libs/angular/angular.min.js','angular')
             .queue('angular','libs/angular/angular-ui-router.min.js','ui-router')
             .queue('ui-router','libs/angular/angular-ui-router-lazy.js','lazy')
             .queue('lazy',null,'libs-angular')

             .queue('libs-angular','lazyapp/lazyapp.module.js','lazyapp-module');

      $script.ready('lazyapp-module',function() { console.log('All Scripts Loaded.'); });
    </script>
  </head>

  <body>
    <div ui-view='mainView'></div>
  </body>
</html>

構文を好むため、関数がScript.jsにハッキングされまし

$script.queue = function(aQueueBehind,aUrl,aLabel) {
  if (aQueueBehind === null) { return $script((aUrl === null?[null]:aUrl),aLabel); }
  $script.ready(aQueueBehind,function() {
    if (aUrl !== null)
      $script(aUrl,aLabel);
    else
      $script.done(aLabel);
  });
  return $script;
}

lazyapp.module.js

(function() {
  var lazyApp = angular && angular.module('lazyApp ',['lazy']);
  lazyApp = angular.module('lazy').makeLazy(lazyApp);

  lazyApp.config(function($stateProvider) {

    $stateProvider.state({
      name: 'root',
      url: '',
      views: {
        'mainView': { templateUrl: '/lazyapp/views/mainview.html', controller: 'lazyAppController' }
      },
      scripts: {
        'directives': [ 'lazyapp/directives/lazyheader/src/lazyheader.js' ],
        'controllers': [ 'lazyapp/controllers/lazyappcontroller.js' ],
        'services': [ 'lazyapp/services/sectionservice.js' ]
      },
      resolve: {
        sections: function(sectionService) {
          return sectionService.getSections();
        }
      }
    });
  });

  angular.bootstrap(document,['lazyApp']);
})();

sectionservice.js

(function() {
  var lazyApp = angular.module('lazyApp');

  lazyApp.service('sectionService',function($q) {
    this.getSections = function() {
      var deferred = $q.defer();
      deferred.resolve({
        'home': {},
        'news': {},
        'events': {},
        'involved': {},
        'contacts': {},
        'links': {}
      });
      return deferred.promise;
    };
  });
})();

lazyheader.js

(function() {
  var lazyApp = angular.module('lazyApp ');

  lazyApp.directive('lazyHeader',function() {
    return {
      templateUrl: 'lazyapp/directives/lazyheader/templates/lazyheader-main.html',
      restrict: 'E'
    };
  });
})();

lazyappcontroller.js

(function() {
  var lazyApp = angular.module('lazyApp ');

  lazyApp.controller('lazyAppController',function(sections) {
    // @TODO: Control things.
    console.log(sections);
  });
})();

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

分類Dev

Angular1コンポーネントの遅延読み込みリクエストエラー

分類Dev

Primefacesコンポーネントでの遅延読み込み

分類Dev

React / Reduxコンポーネントコンストラクターでのアプリケーション状態の読み込み

分類Dev

Angular8の共有コンポーネントによる遅延読み込み

分類Dev

TailwindCSSがAngular(遅延読み込み)の子コンポーネントで機能しない

分類Dev

Vue:コンポーネント内のライブラリ(Bootstrap-Vue)からの遅延読み込み

分類Dev

reactでの遅延読み込み-コンポーネントが読み込まれていません

分類Dev

GatsbyjsスタイルのコンポーネントCSSの読み込み遅延

分類Dev

スタイル付きコンポーネントを使用した画像の遅延読み込みの実装

分類Dev

Angular4遅延読み込みコンポーネントを読み込まない

分類Dev

Angular4遅延読み込み子コンポーネントが読み込まれていません

分類Dev

Angular 2:コンポーネント内から遅延読み込みモジュールのルートを読み取る方法

分類Dev

シリアル化を使用したディープクローン後の休止状態の遅延読み込み例外

分類Dev

モーダルコンポーネントを操作する際の遅延読み込みエラー

分類Dev

react-loadableを使用したコンポーネントの遅延読み込みのための動的パスインポート

分類Dev

遅延読み込みでコンポーネントの宣言が見つかりません

分類Dev

vueとwebpackはコンポーネントの遅延読み込みを行いませんか?

分類Dev

vueスクリプトの遅延読み込み

分類Dev

動的コンポーネントローダーと遅延読み込み

分類Dev

遅延読み込み:react-loadableが他のフォルダーのコンポーネントの読み込みに失敗している

分類Dev

reactコンポーネント内の非同期読み込みスクリプト

分類Dev

Angular10遅延読み込みはコンポーネントにリダイレクトされません

分類Dev

遅延読み込み中に読み込みコンポーネントを表示する方法

分類Dev

オブジェクトの遅延読み込みリストのインターフェース

分類Dev

Angular6の共通コンポーネントを備えた2つの遅延読み込みモジュール

分類Dev

チャンクには遅延読み込みコンポーネントは含まれていません

分類Dev

複数のコンポーネントを含む遅延読み込み機能モジュールは、Angular6では機能しません

分類Dev

遅延読み込みされたコンポーネントセレクターをAngular2 +の別のコンポーネントのHTMLに含める方法

分類Dev

遅延読み込みされたコンポーネントセレクターをAngular2 +の別のコンポーネントのHTMLに含める方法

Related 関連記事

  1. 1

    Angular1コンポーネントの遅延読み込みリクエストエラー

  2. 2

    Primefacesコンポーネントでの遅延読み込み

  3. 3

    React / Reduxコンポーネントコンストラクターでのアプリケーション状態の読み込み

  4. 4

    Angular8の共有コンポーネントによる遅延読み込み

  5. 5

    TailwindCSSがAngular(遅延読み込み)の子コンポーネントで機能しない

  6. 6

    Vue:コンポーネント内のライブラリ(Bootstrap-Vue)からの遅延読み込み

  7. 7

    reactでの遅延読み込み-コンポーネントが読み込まれていません

  8. 8

    GatsbyjsスタイルのコンポーネントCSSの読み込み遅延

  9. 9

    スタイル付きコンポーネントを使用した画像の遅延読み込みの実装

  10. 10

    Angular4遅延読み込みコンポーネントを読み込まない

  11. 11

    Angular4遅延読み込み子コンポーネントが読み込まれていません

  12. 12

    Angular 2:コンポーネント内から遅延読み込みモジュールのルートを読み取る方法

  13. 13

    シリアル化を使用したディープクローン後の休止状態の遅延読み込み例外

  14. 14

    モーダルコンポーネントを操作する際の遅延読み込みエラー

  15. 15

    react-loadableを使用したコンポーネントの遅延読み込みのための動的パスインポート

  16. 16

    遅延読み込みでコンポーネントの宣言が見つかりません

  17. 17

    vueとwebpackはコンポーネントの遅延読み込みを行いませんか?

  18. 18

    vueスクリプトの遅延読み込み

  19. 19

    動的コンポーネントローダーと遅延読み込み

  20. 20

    遅延読み込み:react-loadableが他のフォルダーのコンポーネントの読み込みに失敗している

  21. 21

    reactコンポーネント内の非同期読み込みスクリプト

  22. 22

    Angular10遅延読み込みはコンポーネントにリダイレクトされません

  23. 23

    遅延読み込み中に読み込みコンポーネントを表示する方法

  24. 24

    オブジェクトの遅延読み込みリストのインターフェース

  25. 25

    Angular6の共通コンポーネントを備えた2つの遅延読み込みモジュール

  26. 26

    チャンクには遅延読み込みコンポーネントは含まれていません

  27. 27

    複数のコンポーネントを含む遅延読み込み機能モジュールは、Angular6では機能しません

  28. 28

    遅延読み込みされたコンポーネントセレクターをAngular2 +の別のコンポーネントのHTMLに含める方法

  29. 29

    遅延読み込みされたコンポーネントセレクターをAngular2 +の別のコンポーネントのHTMLに含める方法

ホットタグ

アーカイブ