web-dev-qa-db-ja.com

モデルデータと動作をどこに置くか[tl;博士。利用サービス]

私は最新のプロジェクトのためにAngularJSと協力しています。ドキュメンテーションとチュートリアルでは、すべてのモデルデータはコントローラースコープに置かれます。私はそれが対応する見解の中でコントローラのために利用可能であるためにそこになければならないということを理解します。

しかし、モデルは実際にはそこに実装されるべきだとは思わない。それは複雑で、例えばプライベートな属性を持つかもしれません。さらに、それを別のコンテキスト/アプリで再利用したい場合があります。すべてをコントローラーに入れると、MVCパターンが完全に壊れます。

どのモデルの動作にも同じことが当てはまります。 DCIアーキテクチャー を使用し、データモデルとは別の動作を使用する場合は、動作を保持するために追加のオブジェクトを導入する必要があります。これは、役割とコンテキストを導入することによって行われます。

DCI ==DataC説明Interaction

もちろん、モデルのデータと振る舞いは、プレーンなJavaScriptオブジェクトまたは任意の「クラス」パターンで実装できます。しかし、AngularJSがそれを実行する方法は何でしょうか。サービスを利用する

それでこの質問に帰着します:

AngularJSのベストプラクティスに従って、コントローラから切り離されたモデルをどのように実装しますか?

340
Nils Blum-Oeste

あなたが何かを複数のコントローラで使用可能にしたい場合は、サービスを使用するべきです。これは単純な人為的な例です。

myApp.factory('ListService', function() {
  var ListService = {};
  var list = [];
  ListService.getItem = function(index) { return list[index]; }
  ListService.addItem = function(item) { list.Push(item); }
  ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) }
  ListService.size = function() { return list.length; }

  return ListService;
});

function Ctrl1($scope, ListService) {
  //Can add/remove/get items from shared list
}

function Ctrl2($scope, ListService) {
  //Can add/remove/get items from shared list
}
154
Andrew Joslin

私は現在このパターンを試しています。これはDCIではありませんが、古典的なサービスとモデルの分離(Webサービスと対話するためのサービス(モデルCRUD)、およびオブジェクトのプロパティとメソッドのモデル定義)を提供します。

モデルオブジェクトが独自のプロパティで動作するメソッドを必要とするときはいつでも私はこのパターンを使うだけであることに注意してください。私はnotこれをすべてのサービスに対して体系的に行うことを提唱しています。

編集:私はこのパターンが "Angularモデルは普通のjavascriptオブジェクト"マントラに反すると考えていましたが、今ではこのパターンは完全に問題ないと思われます。

EDIT(2):さらに明確にするために、Modelクラスは単純なゲッター/セッターを因数分解するためだけに使用します(例:ビューテンプレートで使用される)。大規模なビジネスロジックの場合は、モデルについて「知っている」が、それらとは分離されており、ビジネスロジックのみが含まれる別のサービスを使用することをお勧めします。必要に応じて「ビジネスエキスパート」サービスレイヤと呼んでください

service/ElementServices.js(宣言内でElementがどのように挿入されるかに注意してください)

MyApp.service('ElementServices', function($http, $q, Element)
{
    this.getById = function(id)
    {
        return $http.get('/element/' + id).then(
            function(response)
            {
                //this is where the Element model is used
                return new Element(response.data);
            },
            function(response)
            {
                return $q.reject(response.data.error);
            }
        );
    };
    ... other CRUD methods
}

model/Element.js(オブジェクト作成用のangularjs Factoryを使用)

MyApp.factory('Element', function()
{
    var Element = function(data) {
        //set defaults properties and functions
        angular.extend(this, {
            id:null,
            collection1:[],
            collection2:[],
            status:'NEW',
            //... other properties

            //dummy isNew function that would work on two properties to harden code
            isNew:function(){
                return (this.status=='NEW' || this.id == null);
            }
        });
        angular.extend(this, data);
    };
    return Element;
});
80
Ben G

Angularjsのドキュメントには、明確に述べられています。

他の多くのフレームワークと異なり、Angularはモデルに制限や要件を課しません。モデルにアクセスしたりモデルを変更したりするための継承元のクラスや特別なアクセサメソッドはありません。モデルは、プリミティブ、オブジェクトハッシュ、またはフルオブジェクトタイプになります。手短に言えば、モデルは単純なJavaScriptオブジェクトです。

- AngularJS開発者ガイド - V1.5の概念 - モデル

モデルを宣言する方法はあなた次第です。これは単純なJavascriptオブジェクトです。

私は個人的にはAngularサービスを使用しません。それらは、たとえばアプリケーション全体でグローバルな状態を維持するために使用できるシングルトンオブジェクトのように動作することを目的としていました。

29
S.C.

DCIはパラダイムであり、DCIをサポートするかどうかにかかわらず、それを行うための角度的な方法はありません。ソース変換を使用したい場合はJSがDCIをサポートし、使用しない場合はいくつかの欠点があります。また、DCIはC#クラスが持っていると言うよりも依存性注入と関係がなく、間違いなくサービスでもありません。したがって、angulusJSでDCIを実行する最善の方法は、DCIをJSの方法で実行することです。これは、DCIが最初に定式化される方法にかなり近いものです。ソース変換をしない限り、ロールメソッドはコンテキスト外でもオブジェクトの一部になるため、完全には変換できませんが、一般的にはメソッドインジェクションベースのDCIの問題です。 fullOO.info DCIの権威のあるサイトを見れば、Rubyの実装でメソッドインジェクションも使用されているか、または here で詳細を調べることができます。 DCIに関する情報それはほとんどRubyの例を使っていますが、DCIのものはそれには関係ありません。 DCIのポイントの1つは、システムの機能とシステムの機能が分離されていることです。そのため、データオブジェクトはかなり愚かですが、コンテキスト内でロールにバインドされると、ロールメソッドによって特定の動作が使用可能になります。ロールは単なる識別子であり、その識別子を通してオブジェクトにアクセスするときにはロールメソッドが利用可能です。ロールオブジェクト/クラスはありません。メソッドインジェクションでは、ロールメソッドのスコープは説明したとおりではありませんが、近いです。 JSのコンテキストの例は次のようになります。

function transfer(source,destination){
   source.transfer = function(amount){
        source.withdraw(amount);
        source.log("withdrew " + amount);
        destination.receive(amount);
   };
   destination.receive = function(amount){
      destination.deposit(amount);
      destination.log("deposited " + amount);
   };
   this.transfer = function(amount){
    source.transfer(amount);
   };
}
8
Rune FS

AngularJSのモデルに関するこの記事は役に立つかもしれません:

http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/

7
marianboda

以前の質問ですが、このトピックはAngular 2.0という新しい方向性を考えると、これまで以上に関連性があると思います。ベストプラクティスは、特定のフレームワークへの依存度をできるだけ少なくしてコードを書くことです。直接的な価値があるのであれば、フレームワーク固有の部分のみを使用してください。

現在、Angularサービスは次世代のAngularに移行する数少ない概念の1つであると思われるため、すべてのロジックをサービスに移行するという一般的なガイドラインに従うのが賢明でしょう。ただし、Angularサービスに直接依存しなくても分離モデルを作成できると私は主張します。必要な依存関係と責任だけを持った自己完結型のオブジェクトを作成することは、おそらく進むべき道です。自動テストをするとき、それはまた人生をずっと楽にします。単一の責任は最近の話題の仕事ですが、それは多くの意味があります!

これは、オブジェクトモデルをDOMから分離するのに適していると考えられるパターンの例です。

http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e

主な目標は、単体テストから見た場合と同じくらい簡単に使用できるようにコードを構造化することです。あなたがそれを達成するならば、あなたは現実的で有用なテストを書くためによく位置しています。

5
TGH

他のポスターが述べているように、Angularはモデリングのための基本クラスを提供しませんが、いくつかの機能を便利に提供することができます。

  1. RESTful APIと対話して新しいオブジェクトを作成するためのメソッド
  2. モデル間の関係の確立
  3. バックエンドに永続化する前にデータを検証する。リアルタイムのエラーを表示するのにも役立ちます
  4. 無駄なHTTPリクエストを行わないようにするためのキャッシングと遅延ロード
  5. ステートマシンフック(保存、更新、作成、新規などの前後)

これらすべてをうまく処理するライブラリの1つにngActiveResource( https://github.com/FacultyCreative/ngActiveResource )があります。完全な開示 - 私はこのライブラリを書きました - そして私はいくつかのエンタープライズ規模のアプリケーションを構築するのにそれをうまく使いました。十分にテストされており、Rails開発者になじみのあるAPIを提供します。

私のチームと私はこのライブラリを積極的に開発し続けています、そしてより多くのAngular開発者がこのライブラリに貢献し、それを戦闘テストしたいと思います。

5
Brett Cassette

私は このブログ記事 でその正確な問題に取り組もうとしました。

基本的に、データモデリングの最善の家はサービスと工場です。ただし、データの取得方法と必要な動作の複雑さに応じて、実装にはさまざまな方法があります。 Angularには現在標準の方法やベストプラクティスはありません。

この記事では、$ http$ resource、およびRestangular

JobモデルのカスタムgetResult()メソッドを使った、それぞれのサンプルコードは次のとおりです。

Restangular(やさしい):

angular.module('job.models', [])
  .service('Job', ['Restangular', function(Restangular) {
    var Job = Restangular.service('jobs');

    Restangular.extendModel('jobs', function(model) {
      model.getResult = function() {
        if (this.status == 'complete') {
          if (this.passed === null) return "Finished";
          else if (this.passed === true) return "Pass";
          else if (this.passed === false) return "Fail";
        }
        else return "Running";
      };

      return model;
    });

    return Job;
  }]);

$ resource(もう少し複雑):

angular.module('job.models', [])
    .factory('Job', ['$resource', function($resource) {
        var Job = $resource('/api/jobs/:jobId', { full: 'true', jobId: '@id' }, {
            query: {
                method: 'GET',
                isArray: false,
                transformResponse: function(data, header) {
                    var wrapped = angular.fromJson(data);
                    angular.forEach(wrapped.items, function(item, idx) {
                        wrapped.items[idx] = new Job(item);
                    });
                    return wrapped;
                }
            }
        });

        Job.prototype.getResult = function() {
            if (this.status == 'complete') {
                if (this.passed === null) return "Finished";
                else if (this.passed === true) return "Pass";
                else if (this.passed === false) return "Fail";
            }
            else return "Running";
        };

        return Job;
    }]);

$ http(ハードコア):

angular.module('job.models', [])
    .service('JobManager', ['$http', 'Job', function($http, Job) {
        return {
            getAll: function(limit) {
                var params = {"limit": limit, "full": 'true'};
                return $http.get('/api/jobs', {params: params})
                  .then(function(response) {
                    var data = response.data;
                    var jobs = [];
                    for (var i = 0; i < data.objects.length; i ++) {
                        jobs.Push(new Job(data.objects[i]));
                    }
                    return jobs;
                });
            }
        };
    }])
    .factory('Job', function() {
        function Job(data) {
            for (attr in data) {
                if (data.hasOwnProperty(attr))
                    this[attr] = data[attr];
            }
        }

        Job.prototype.getResult = function() {
            if (this.status == 'complete') {
                if (this.passed === null) return "Finished";
                else if (this.passed === true) return "Pass";
                else if (this.passed === false) return "Fail";
            }
            else return "Running";
        };

        return Job;
    });

ブログ投稿自体では、なぜあなたがそれぞれのアプローチを使うのかの背後にある推論、そしてあなたのコントローラーの中でモデルを使う方法のコード例についてより詳細に行きます:

AngularJSデータモデル:$ http VS $ resource VS Restangular

Angular 2.0を使用すると、データモデリングに対してより堅牢なソリューションを提供し、全員が同じページにアクセスできるようになる可能性があります。