ホームページ >ウェブフロントエンド >jsチュートリアル >angularjs+requirejs を探索してオンデマンド読み込みルーチンを完全に実装する_AngularJS

angularjs+requirejs を探索してオンデマンド読み込みルーチンを完全に実装する_AngularJS

PHP中文网
PHP中文网オリジナル
2016-05-16 15:13:121486ブラウズ

一定規模のプロジェクトに取り組む場合、通常は次の目標を達成する必要があります。 1. 複雑なページ ロジックをサポートする (権限、データ ステータスなどのビジネス ルールに基づいたコンテンツの動的な表示)。フロントエンドとバックエンドの分離の基本原則を遵守します (分離されていない場合は、テンプレート エンジンを使用してバックエンドで適切なページを直接生成できます) 3. ページの読み込み時間が短い (ビジネス ロジックが複雑な場合)。 、サードパーティのライブラリを参照する必要がありますが、ロードされたライブラリはユーザーの現在の操作とは何の関係もない可能性が非常に高いです) ) 4. コードは保守しやすくなければなりません (新しいロジックを追加するときは、影響を最小限に抑える必要があります)。可能な限りファイル)。

これらの目標を同時に達成するには、ページに表示されるコンテンツとすべての依存ファイルをビジネス ロジックのニーズに応じてオンデマンドでロードできるメカニズムが必要です。最近はすべての開発が angularjs に基づいているため、この記事では主に angularjs が提供するさまざまなメカニズムに焦点を当て、オンデマンド読み込みを完全に実装する方法を検討します。

1. 段階的な実装
基本的な考え方: 1. まず、いくつかの基本的なビジネス ロジックを完成させ、拡張メカニズムをサポートできるフレームワーク ページを開発します。 、一部のロジックはサブページに分割する必要があり、それらはオンデマンドでロードされます。 3. サブページの表示コンテンツも複雑になり、オンデマンドで分割してロードする必要があります。サブページは複雑です 外部モジュールに依存するには、オンデマンドで Angular モジュールをロードする必要があります。

1. フレームページ
フロントエンドでのオンデマンドロードといえば、最近では AMD (Asynchronous Module Definition) が思い浮かびます。多くの requirejs が使用されるため、最初に要件の導入を検討してください。

index.html

<script src="static/js/require.js" defer async data-main="/test/lazyspa/spa-loader.js"></script>

注: Angular は手動で起動されるため、HTML には ng-app はありません。

spa-loader.js

require.config({
  paths: {
    "domReady": &#39;/static/js/domReady&#39;,
    "angular": "//cdn.bootcss.com/angular.js/1.4.8/angular.min",
    "angular-route": "//cdn.bootcss.com/angular.js/1.4.8/angular-route.min",
  },
  shim: {
    "angular": {
      exports: "angular"
    },
    "angular-route": {
      deps: ["angular"]
    },
  },
  deps: [&#39;/test/lazyspa/spa.js&#39;],
  urlArgs: "bust=" + (new Date()).getTime()
});

spa.js

define(["require", "angular", "angular-route"], function(require, angular) {
  var app = angular.module(&#39;app&#39;, [&#39;ngRoute&#39;]);
  require([&#39;domReady!&#39;], function(document) {
    angular.bootstrap(document, ["app"]); /*手工启动angular*/
    window.loading.finish();
  });
});

2. オンデマンドでサブページをロードします
Angular のrouteProvider+ng-view は、直接使用できる完全なサブページ読み込みメソッドをすでに提供しています。
html5Mode を設定する必要があることに注意してください。設定しないと、URL が変更された後に、routeProvider がその URL をインターセプトしません。

index.html

<div>
  <a href="/test/lazyspa/page1">page1</a>
  <a href="/test/lazyspa/page2">page2</a>
  <a href="/test/lazyspa/">main</a>
</div>
<div ng-view></div>

spa.js

app.config([&#39;$locationProvider&#39;, &#39;$routeProvider&#39;, function($locationProvider, $routeProvider) {
  /* 必须设置生效,否则下面的设置不生效 */
  $locationProvider.html5Mode(true);
  /* 根据url的变化加载内容 */
  $routeProvider.when(&#39;/test/lazyspa/page1&#39;, {
    template: &#39;<div>page1</div>&#39;,
  }).when(&#39;/test/lazyspa/page2&#39;, {
    template: &#39;<div>page2</div>&#39;,
  }).otherwise({
    template: &#39;<div>main</div>&#39;,
  });
}]);

3. オンデマンドでサブページにコンテンツをロードする
を使用しますRouteProvider の前提として、URL を変更する必要がありますが、場合によってはサブページの一部のみを変更する必要があります。これらの変更が主にバインドされたデータに関連しており、ページ レイアウトに影響を及ぼさない場合、または影響が非常に小さい場合は、基本的に ng-if などのタグで問題を解決できます。ただし、ユーザーのログイン前後のローカル変更など、ページのステータスに基づいてローカル コンテンツを完全に変更する必要がある場合があります。これは、ローカル レイアウトが非常に複雑になる可能性があり、独立した単位として扱う必要があることを意味します。 。

ページに部分的なコンテンツを読み込む際の問題を解決するには、ng-include を使用します。ただし、より複雑な状況を考えることもできます。このページフラグメントに対応するコードはバックエンドによって動的に生成され、HTMLだけでなくコードフラグメントに対応するコントローラーもjsで定義されます。この場合、HTML を動的にロードするという問題だけでなく、コントローラーを動的に定義するという問題も考慮する必要があります。コントローラーはangularのcontrollerProviderのregisterメソッドで登録するため、controllerProviderのインスタンスを取得する必要があります。

spa.js

app.config([&#39;$locationProvider&#39;, &#39;$routeProvider&#39;, &#39;$controllerProvider&#39;, 
function($locationProvider, $routeProvider, $controllerProvider) {
  app.providers = {
    $controllerProvider: $controllerProvider //注意这里!!!
  };
  /* 必须设置生效,否则下面的设置不生效 */
  $locationProvider.html5Mode(true);
  /* 根据url的变化加载内容 */
  $routeProvider.when(&#39;/test/lazyspa/page1&#39;, {
    /*!!!页面中引入动态内容!!!*/
    template: &#39;<div>page1</div><div ng-include="\&#39;page1.html\&#39;"></div>&#39;,
    controller: &#39;ctrlPage1&#39;
  }).when(&#39;/test/lazyspa/page2&#39;, {
    template: &#39;<div>page2</div>&#39;,
  }).otherwise({
    template: &#39;<div>main</div>&#39;,
  });
  app.controller(&#39;ctrlPage1&#39;, [&#39;$scope&#39;, &#39;$templateCache&#39;, function($scope, $templateCache) {
    /* 用这种方式,ng-include配合,根据业务逻辑动态获取页面内容 */
    /* !!!动态的定义controller!!! */
    app.providers.$controllerProvider.register(&#39;ctrlPage1Dyna&#39;, [&#39;$scope&#39;, function($scope) {
      $scope.openAlert = function() {
        alert(&#39;page1 alert&#39;);
      };
    }]);
    /* !!!动态定义页面的内容!!! */
    $templateCache.put(&#39;page1.html&#39;, &#39;<div ng-controller="ctrlPage1Dyna">
    <button ng-click="openAlert()">alert</button></div>&#39;);
  }]);
}]);

4. 動的読み込みモジュール
は上記のサブページを使用します。フラグメント ロード方法には制限があります。つまり、さまざまなロジック (js) を起動モジュールに追加する必要があり、サブページ フラグメントの独立したカプセル化が制限されます。特に、サブページ フラグメントでサードパーティ モジュールの使用が必要であり、このモジュールが起動モジュールに事前にロードされていない場合、解決策はありません。したがって、モジュールを動的にロードできる必要があります。モジュールの動的ロードを実装するには、Angular の起動時にモジュールをロードするメソッドを抽出し、いくつかの特殊な状況を処理します。

しかし、実際に実行してみると記事内のコードに問題があることが分かりました。「$injector」とは一体何でしょうか? angular injector.js のソースコードを調べたところ、何が起こっているのかがおおよそわかりました。

アプリケーションには、providerInjector と instanceInjector という 2 つの $injector があります。 invokeQueueはproviderInjectorを使用し、runBlocksはinstanceProviderを使用します。 $injector が間違って使用されると、必要なサービスが見つかりません。

routeProvider でのモジュール ファイルの動的ロード。

template: &#39;<div ng-controller="ctrlModule1"><div>page2</div><div>
<button ng-click="openDialog()">open dialog</button></div></div>&#39;,
resolve: {
  load: [&#39;$q&#39;, function($q) {
    var defer = $q.defer();
    /* 动态加载angular模块 */
    require([&#39;/test/lazyspa/module1.js&#39;], function(loader) {
      loader.onload && loader.onload(function() {
        defer.resolve();
      });
    });
    return defer.promise;
  }]
}

Angular モジュールの動的読み込み

angular._lazyLoadModule = function(moduleName) {
  var m = angular.module(moduleName);
  console.log(&#39;register module:&#39; + moduleName);
  /* 应用的injector,和config中的injector不是同一个,是instanceInject,返回的是通过provider.$get创建的实例 */
  var $injector = angular.element(document).injector();
  /* 递归加载依赖的模块 */
  angular.forEach(m.requires, function(r) {
    angular._lazyLoadModule(r);
  });
  /* 用provider的injector运行模块的controller,directive等等 */
  angular.forEach(m._invokeQueue, function(invokeArgs) {
    try {
      var provider = providers.$injector.get(invokeArgs[0]);
      provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
    } catch (e) {
      console.error(&#39;load module invokeQueue failed:&#39; + e.message, invokeArgs);
    }
  });
  /* 用provider的injector运行模块的config */
  angular.forEach(m._configBlocks, function(invokeArgs) {
    try {
      providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]);
    } catch (e) {
      console.error(&#39;load module configBlocks failed:&#39; + e.message, invokeArgs);
    }
  });
  /* 用应用的injector运行模块的run */
  angular.forEach(m._runBlocks, function(fn) {
    $injector.invoke(fn);
  });
};

モジュールの定義
module1.js

define(["angular"], function(angular) {
  var onloads = [];
  var loadCss = function(url) {
    var link, head;
    link = document.createElement(&#39;link&#39;);
    link.href = url;
    link.rel = &#39;stylesheet&#39;;
    head = document.querySelector(&#39;head&#39;);
    head.appendChild(link);
  };
  loadCss(&#39;//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css&#39;);
  /* !!! 动态定义requirejs !!!*/
  require.config({
    paths: {
      &#39;ui-bootstrap-tpls&#39;: &#39;//cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min&#39;
    },
    shim: {
      "ui-bootstrap-tpls": {
        deps: [&#39;angular&#39;]
      }
    }
  });
  /*!!! 模块中需要引用第三方的库,加载模块依赖的模块 !!!*/
  require([&#39;ui-bootstrap-tpls&#39;], function() {
    var m1 = angular.module(&#39;module1&#39;, [&#39;ui.bootstrap&#39;]);
    m1.config([&#39;$controllerProvider&#39;, function($controllerProvider) {
      console.log(&#39;module1 - config begin&#39;);
    }]);
    m1.controller(&#39;ctrlModule1&#39;, [&#39;$scope&#39;, &#39;$uibModal&#39;, function($scope, $uibModal) {
      console.log(&#39;module1 - ctrl begin&#39;);
      /*!!! 打开angular ui的对话框 !!!*/
      var dlg = &#39;<div class="modal-header">&#39;;
      dlg += &#39;<h3 class="modal-title">I\&#39;m a modal!</h3>&#39;;
      dlg += &#39;</div>&#39;;
      dlg += &#39;<div class="modal-body">content</div>&#39;;
      dlg += &#39;<div class="modal-footer">&#39;;
      dlg += &#39;<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>&#39;;
      dlg += &#39;<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>&#39;;
      dlg += &#39;</div>&#39;;
      $scope.openDialog = function() {
        $uibModal.open({
          template: dlg,
          controller: [&#39;$scope&#39;, &#39;$uibModalInstance&#39;, function($scope, $mi) {
            $scope.cancel = function() {
              $mi.dismiss();
            };
            $scope.ok = function() {
              $mi.close();
            };
          }],
          backdrop: &#39;static&#39;
        });
      };
    }]);
    /* !!!动态加载模块!!! */
    angular._lazyLoadModule(&#39;module1&#39;);
    console.log(&#39;module1 loaded&#39;);
    angular.forEach(onloads, function(onload) {
      angular.isFunction(onload) && onload();
    });
  });
  return {
    onload: function(callback) {
      onloads.push(callback);
    }
  };
});

2. 完全なコード
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta content="width=device-width,user-scalable=no,initial-scale=1.0" name="viewport">
    <base href=&#39;/&#39;>
    <title>SPA</title>
  </head>
  <body>
    <div ng-controller=&#39;ctrlMain&#39;>
      <div>
        <a href="/test/lazyspa/page1">page1</a>
        <a href="/test/lazyspa/page2">page2</a>
        <a href="/test/lazyspa/">main</a>
      </div>
      <div ng-view></div>
    </div>
    <div class="loading"><div class=&#39;loading-indicator&#39;><i></i></div></div>
    <script src="static/js/require.js" defer async data-main="/test/lazyspa/spa-loader.js?_=3"></script>
  </body>
</html>

spa-loader.js

window.loading = {
  finish: function() {
    /* 保留个方法做一些加载完成后的处理,我实际的项目中会在这里结束加载动画 */
  },
  load: function() {
    require.config({
      paths: {
        "domReady": &#39;/static/js/domReady&#39;,
        "angular": "//cdn.bootcss.com/angular.js/1.4.8/angular.min",
        "angular-route": "//cdn.bootcss.com/angular.js/1.4.8/angular-route.min",
      },
      shim: {
        "angular": {
          exports: "angular"
        },
        "angular-route": {
          deps: ["angular"]
        },
      },
      deps: [&#39;/test/lazyspa/spa.js&#39;],
      urlArgs: "bust=" + (new Date()).getTime()
    });
  }
};
window.loading.load();

spa.js

&#39;use strict&#39;;
define(["require", "angular", "angular-route"], function(require, angular) {
  var app = angular.module(&#39;app&#39;, [&#39;ngRoute&#39;]);
  /* 延迟加载模块 */
  angular._lazyLoadModule = function(moduleName) {
    var m = angular.module(moduleName);
    console.log(&#39;register module:&#39; + moduleName);
    /* 应用的injector,和config中的injector不是同一个,是instanceInject,返回的是通过provider.$get创建的实例 */
    var $injector = angular.element(document).injector();
    /* 递归加载依赖的模块 */
    angular.forEach(m.requires, function(r) {
      angular._lazyLoadModule(r);
    });
    /* 用provider的injector运行模块的controller,directive等等 */
    angular.forEach(m._invokeQueue, function(invokeArgs) {
      try {
        var provider = providers.$injector.get(invokeArgs[0]);
        provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
      } catch (e) {
        console.error(&#39;load module invokeQueue failed:&#39; + e.message, invokeArgs);
      }
    });
    /* 用provider的injector运行模块的config */
    angular.forEach(m._configBlocks, function(invokeArgs) {
      try {
        providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]);
      } catch (e) {
        console.error(&#39;load module configBlocks failed:&#39; + e.message, invokeArgs);
      }
    });
    /* 用应用的injector运行模块的run */
    angular.forEach(m._runBlocks, function(fn) {
      $injector.invoke(fn);
    });
  };
  app.config([&#39;$injector&#39;, &#39;$locationProvider&#39;, &#39;$routeProvider&#39;, &#39;$controllerProvider&#39;, 
  function($injector, $locationProvider, $routeProvider, $controllerProvider) {
    /**
     * config中的injector和应用的injector不是同一个,是providerInjector,获得的是provider,
     而不是通过provider创建的实例
     * 这个injector通过angular无法获得,所以在执行config的时候把它保存下来
    */
    app.providers = {
      $injector: $injector,
      $controllerProvider: $controllerProvider
    };
    /* 必须设置生效,否则下面的设置不生效 */
    $locationProvider.html5Mode(true);
    /* 根据url的变化加载内容 */
    $routeProvider.when(&#39;/test/lazyspa/page1&#39;, {
      template: &#39;<div>page1</div><div ng-include="\&#39;page1.html\&#39;"></div>&#39;,
      controller: &#39;ctrlPage1&#39;
    }).when(&#39;/test/lazyspa/page2&#39;, {
      template: &#39;<div ng-controller="ctrlModule1"><div>page2</div><div>
      <button ng-click="openDialog()">open dialog</button></div></div>&#39;,
      resolve: {
        load: [&#39;$q&#39;, function($q) {
          var defer = $q.defer();
          /* 动态加载angular模块 */
          require([&#39;/test/lazyspa/module1.js&#39;], function(loader) {
            loader.onload && loader.onload(function() {
              defer.resolve();
            });
          });
          return defer.promise;
        }]
      }
    }).otherwise({
      template: &#39;<div>main</div>&#39;,
    });
  }]);
  app.controller(&#39;ctrlMain&#39;, [&#39;$scope&#39;, &#39;$location&#39;, function($scope, $location) {
    console.log(&#39;main controller&#39;);
    /* 根据业务逻辑自动到缺省的视图 */
    $location.url(&#39;/test/lazyspa/page1&#39;);
  }]);
  app.controller(&#39;ctrlPage1&#39;, [&#39;$scope&#39;, &#39;$templateCache&#39;, function($scope, $templateCache) {
    /* 用这种方式,ng-include配合,根据业务逻辑动态获取页面内容 */
    /* 动态的定义controller */
    app.providers.$controllerProvider.register(&#39;ctrlPage1Dyna&#39;, [&#39;$scope&#39;, function($scope) {
      $scope.openAlert = function() {
        alert(&#39;page1 alert&#39;);
      };
    }]);
    /* 动态定义页面内容 */
    $templateCache.put(&#39;page1.html&#39;, &#39;<div ng-controller="ctrlPage1Dyna">
    <button ng-click="openAlert()">alert</button></div>&#39;);
  }]);
  require([&#39;domReady!&#39;], function(document) {
    angular.bootstrap(document, ["app"]);
  });
});

module1.js

&#39;use strict&#39;;
define(["angular"], function(angular) {
  var onloads = [];
  var loadCss = function(url) {
    var link, head;
    link = document.createElement(&#39;link&#39;);
    link.href = url;
    link.rel = &#39;stylesheet&#39;;
    head = document.querySelector(&#39;head&#39;);
    head.appendChild(link);
  };
  loadCss(&#39;//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css&#39;);
  require.config({
    paths: {
      &#39;ui-bootstrap-tpls&#39;: &#39;//cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min&#39;
    },
    shim: {
      "ui-bootstrap-tpls": {
        deps: [&#39;angular&#39;]
      }
    }
  });
  require([&#39;ui-bootstrap-tpls&#39;], function() {
    var m1 = angular.module(&#39;module1&#39;, [&#39;ui.bootstrap&#39;]);
    m1.config([&#39;$controllerProvider&#39;, function($controllerProvider) {
      console.log(&#39;module1 - config begin&#39;);
    }]);
    m1.controller(&#39;ctrlModule1&#39;, [&#39;$scope&#39;, &#39;$uibModal&#39;, function($scope, $uibModal) {
      console.log(&#39;module1 - ctrl begin&#39;);
      var dlg = &#39;<div class="modal-header">&#39;;
      dlg += &#39;<h3 class="modal-title">I\&#39;m a modal!</h3>&#39;;
      dlg += &#39;</div>&#39;;
      dlg += &#39;<div class="modal-body">content</div>&#39;;
      dlg += &#39;<div class="modal-footer">&#39;;
      dlg += &#39;<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>&#39;;
      dlg += &#39;<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>&#39;;
      dlg += &#39;</div>&#39;;
      $scope.openDialog = function() {
        $uibModal.open({
          template: dlg,
          controller: [&#39;$scope&#39;, &#39;$uibModalInstance&#39;, function($scope, $mi) {
            $scope.cancel = function() {
              $mi.dismiss();
            };
            $scope.ok = function() {
              $mi.close();
            };
          }],
          backdrop: &#39;static&#39;
        });
      };
    }]);
    angular._lazyLoadModule(&#39;module1&#39;);
    console.log(&#39;module1 loaded&#39;);
    angular.forEach(onloads, function(onload) {
      angular.isFunction(onload) && onload();
    });
  });
  return {
    onload: function(callback) {
      onloads.push(callback);
    }
  };
});

上記は、オンデマンド読み込みルーチン_AngularJS を完全に実装するために angularjs+requirejs を探索する内容です。関連コンテンツについては、PHP 中国語 Web サイト (www.php.cn) に注目してください。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。