1. ホーム
  2. javascript

[解決済み] AngularJS: デザインパターンを理解する

2022-05-30 06:02:35

質問

の文脈では この記事 AngularJSのリードであるIgor Minarによるものです。

MVC vs MVVM vs MVP . 多くの開発者が何時間もかけて議論し、論争するような話題です。 について何時間も議論し、議論することができます。

数年間、AngularJSはMVCに近いものでした(というか、そのクライアント側の変種の1つ)。 しかし、時が経ち、多くのリファクタリングとAPIの改良のおかげで とAPIの改良のおかげで、現在では MVVM - は $scope オブジェクト とみなすことができます。 ビューモデル で装飾された と呼ばれる関数によって装飾されている コントローラ .

フレームワークを分類して、MV*のバケツの一つに入れることができるのは、いくつかの利点があります。 それは、開発者がそのAPIをより快適に使用できるようにすることです。 フレームワークで構築されるアプリケーションを表すメンタルモデルを作成しやすくすることで、開発者がそのAPIをより快適に使用できるようになります。 フレームワークで構築されるアプリケーションを表すメンタルモデルを作成しやすくなります。また、開発者が使用する用語の確立にも役立ちます。 開発者が使用する用語集を確立するのにも役立ちます。

とはいえ、私はむしろ、開発者がよく設計され、関心事の分離に従った、すばらしいアプリを構築するのを見たいと思っています。 開発者が MV* の無意味な議論に時間を費やすのを見るよりも、よく設計され、関心事の分離に従った、すばらしいアプリを構築するのを見たいのです。 について議論して時間を浪費するのを見るよりも、よく設計され、関心事の分離に従った、すばらしいアプリを開発するのを見たいのです。そして、この理由から、私はここに宣言します。 AngularJS MVWフレームワーク - Model-View-Whatever . ここで、Whatever は " を表します。 あなたのために働くものなら何でも の略です。

Angularは、ビジネスロジックとプレゼンテーションの状態からプレゼンテーションロジックをうまく分離するために、多くの柔軟性を与えてくれます。 ロジックをビジネスロジックとプレゼンテーションの状態からうまく切り離すことができます。それを使って 生産性とアプリケーションの保守性を高めるために使ってください。 という議論ではなく、生産性とアプリケーションの保守性を高めるために使ってください。

AngularJSのMVW(Model-View-Whatever)デザインパターンをクライアントサイドアプリケーションで実装するための推奨事項やガイドラインはありますか?

どのように解決するのですか?

膨大な量の貴重なソースのおかげで、AngularJSアプリにコンポーネントを実装するための一般的な推奨事項をいくつか手に入れました。


コントローラ

  • コントローラは単に インターレイヤー であるべきです。として作ってみてください。 薄い にしてください。

  • を強く推奨します。 ビジネスロジックを避ける を避けることを強くお勧めします。それはモデルに移されるべきです。

  • コントローラは、メソッド呼び出し(子供が親と通信したいときに可能)または $emit , $broadcast および $on メソッドを使用します。放出されるメッセージやブロードキャストされるメッセージは最小限にとどめる必要があります。

  • コントローラは プレゼンテーションにこだわらない またはDOM操作に関心がないこと。

  • を試してみてください。 コントローラのネストを避ける . この場合、親コントローラはモデルとして解釈されます。代わりにモデルをシェアードサービスとしてインジェクトします。

  • スコープ をコントローラの中で使用する必要があります。 バインディング モデルをビューと

    をカプセル化する ビューモデル については プレゼンテーションモデル のデザインパターンと同じです。


範囲

スコープを 読み出し専用として扱う コントローラでは書き込み専用 . スコープの目的はモデルを参照することであり、モデルになることではありません。

双方向バインディング(ng-model)をするときは、スコープのプロパティに直接バインドしないように注意してください。


モデル

AngularJSにおけるモデルとは シングルトン で定義されています。 サービス .

モデルは、データと表示を分離する優れた方法を提供します。

モデルはユニットテストの有力な候補です。なぜなら、通常、依存関係 (ある種のイベントエミッター、よくあるケースでは $rootScope ) を持ち、高度にテスト可能な ドメインロジック .

  • モデルは特定のユニットの実装と考えるべきでしょう。 これは単一責任原則に基づくものです。ユニットとは、実世界で一つの実体を表し、プログラミングの世界ではそれを データと状態 .

  • モデルは、アプリケーションのデータをカプセル化して API を提供しなければなりません。

  • モデルは ポータブル であるべきです。 であることが望ましい。

  • ユニットロジックをモデル内に分離することで、ロジックの配置や更新、保守が容易になります。 を見つけ、更新し、維持することができます。

  • モデルは、アプリケーション全体に共通する、より一般的なグローバルモデルのメソッドを使用することができます。 を使用することができます。

  • コンポーネントの結合を減らし、ユニットを増やすために、依存性注入を使って他のモデルを自分のモデルに合成することは、本当に依存していないのであれば、避けるようにしてください。 テスタビリティ ユーザビリティ .

  • モデルでイベントリスナーを使用することは避けるようにしてください。それはテストを困難にし、一般的に単一責任原則の観点からモデルを殺すことになります。

モデルの実装

モデルはデータと状態の観点からいくつかのロジックをカプセル化する必要があるため、アーキテクチャ的にメンバーへのアクセスを制限する必要があり、これにより疎結合を保証することができます。

AngularJSアプリケーションでこれを行う方法は、モデルを ファクトリー サービスタイプで定義することです。これにより、プライベートなプロパティやメソッドを簡単に定義することができ、またパブリックにアクセス可能なものを一箇所で返すことができるので、開発者にとって本当に読みやすいものになります。

:

angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {

  var itemsPerPage = 10,
  currentPage = 1,
  totalPages = 0,
  allLoaded = false,
  searchQuery;

  function init(params) {
    itemsPerPage = params.itemsPerPage || itemsPerPage;
    searchQuery = params.substring || searchQuery;
  }

  function findItems(page, queryParams) {
    searchQuery = queryParams.substring || searchQuery;

    return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
      totalPages = results.totalPages;
      currentPage = results.currentPage;
      allLoaded = totalPages <= currentPage;

      return results.list
    });
  }

  function findNext() {
    return findItems(currentPage + 1);
  }

  function isAllLoaded() {
    return allLoaded;
  }

  // return public model API  
  return {
    /**
     * @param {Object} params
     */
    init: init,

    /**
     * @param {Number} page
     * @param {Object} queryParams
     * @return {Object} promise
     */
    find: findItems,

    /**
     * @return {Boolean}
     */
    allLoaded: isAllLoaded,

    /**
     * @return {Object} promise
     */
    findNext: findNext
  };
});

新しいインスタンスの作成

これは依存性注入を破壊し始め、ライブラリは、特にサードパーティにとって厄介な振る舞いをするでしょうから、新しいできる関数を返すファクトリーを持つことを避けるようにしてください。

同じことを達成するためのより良い方法は、ゲッターとセッターのメソッドを持つオブジェクトのコレクションを返すAPIとしてファクトリーを使用することです。

angular.module('car')
 .factory( 'carModel', ['carResource', function (carResource) {

  function Car(data) {
    angular.extend(this, data);
  }

  Car.prototype = {
    save: function () {
      // TODO: strip irrelevant fields
      var carData = //...
      return carResource.save(carData);
    }
  };

  function getCarById ( id ) {
    return carResource.getById(id).then(function (data) {
      return new Car(data);
    });
  }

  // the public API
  return {
    // ...
    findById: getCarById
    // ...
  };
});

グローバルモデル

一般的には、このような状況を避け、モデルを適切に設計し、コントローラに注入してビューで使用することができるようにします。

特に、アプリケーション内でグローバルなアクセスを必要とするメソッドがあります。 それを可能にするために、' 共通 プロパティを $rootScope にバインドし、それを commonModel にバインドします。

angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
  $rootScope.common = 'commonModel';
}]);

すべてのグローバルメソッドは、' 共通 プロパティに格納されます。これはある種の 名前空間 .

ただし、メソッドを直接 $rootScope . これは 予期せぬ動作 を使用した場合、一般的にスコープが散らかり、スコープメソッドのオーバーライドの問題につながります。


リソース

リソースでは、さまざまな データソース .

を使用して実装する必要があります。 単一責任原則 .

特定の場合、それは 再利用可能な HTTP/JSONエンドポイントへのプロキシです。

リソースはモデルに注入され、データの送信/取得の可能性を提供します。

リソースの実装

RESTfulなサーバーサイドのデータソースと対話できるリソースオブジェクトを作成するファクトリです。

返されたリソースオブジェクトは、低レベルの$httpサービスと対話する必要なしに高レベルの動作を提供するアクションメソッドを持っています。


サービス

モデルもリソースもサービスです。 .

サービスは関連付けられていない 疎結合 自己完結している機能単位です。

サービスは、昔からよく使われていたサーバーサイドから、AngularがクライアントサイドのWebアプリにもたらす機能です。

Angularアプリにおけるサービスは、依存性注入を使用して一緒に配線された置換可能なオブジェクトです。

Angularにはさまざまな種類のサービスがあります。それぞれ独自の使用例があります。以下をお読みください。 サービスの種類を理解する をご覧ください。

を検討してみてください。 サービスアーキテクチャの主な原則 を考慮してください。

一般に ウェブサービス用語集 :

サービスは抽象的なリソースです。 タスクを実行する能力を表す抽象的なリソースです。 提供者エンティティおよび要求者エンティティの観点から一貫した機能を形成するタスクを実行する能力を表す抽象的なリソースです。利用するためには サービスは具体的な提供者エージェントによって実現されなければなりません。


クライアント側の構造

一般に、アプリケーションのクライアントサイドは、以下のように分割されます。 モジュール . 各モジュールは テスト可能 でなければなりません。

に依存したモジュールを定義するようにしてください。 特徴・機能 または ビュー であり、タイプによるものではありません。 参照 Miskoのプレゼンテーション をご覧ください。

モジュールのコンポーネントは、コントローラ、モデル、ビュー、フィルタ、ディレクティブなどのタイプによって慣習的にグループ化されることがあります。

しかし、モジュール自体は 再利用可能 , 転送可能 そして テスト可能 .

また、開発者がコードのある部分とその依存関係をすべて見つけることがより簡単になります。

を参照してください。 大規模なAngularJSとJavaScriptアプリケーションにおけるコード構成 を参照してください。

フォルダの構造化の例 :

|-- src/
|   |-- app/
|   |   |-- app.js
|   |   |-- home/
|   |   |   |-- home.js
|   |   |   |-- homeCtrl.js
|   |   |   |-- home.spec.js
|   |   |   |-- home.tpl.html
|   |   |   |-- home.less
|   |   |-- user/
|   |   |   |-- user.js
|   |   |   |-- userCtrl.js
|   |   |   |-- userModel.js
|   |   |   |-- userResource.js
|   |   |   |-- user.spec.js
|   |   |   |-- user.tpl.html
|   |   |   |-- user.less
|   |   |   |-- create/
|   |   |   |   |-- create.js
|   |   |   |   |-- createCtrl.js
|   |   |   |   |-- create.tpl.html
|   |-- common/
|   |   |-- authentication/
|   |   |   |-- authentication.js
|   |   |   |-- authenticationModel.js
|   |   |   |-- authenticationService.js
|   |-- assets/
|   |   |-- images/
|   |   |   |-- logo.png
|   |   |   |-- user/
|   |   |   |   |-- user-icon.png
|   |   |   |   |-- user-default-avatar.png
|   |-- index.html

angularアプリケーションの構造化の良い例は、以下のように実装されています。 angular-app - https://github.com/angular-app/angular-app/tree/master/client/src

これは、最近のアプリケーションジェネレータでも考慮されています -。 https://github.com/yeoman/generator-angular/issues/109