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


341

私の最新プロジェクトではAngularJSを使用しています。ドキュメントとチュートリアルでは、すべてのモデルデータがコントローラースコープに入れられます。コントローラーで使用できるように、つまり対応するビュー内にある必要があることを理解しています。

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

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

DCI == D ATA C ollaboration I nteraction

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

したがって、この質問に帰着します。

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


12
DCIを定義したり、少なくともスペルアウトしたフォームを提供したりできる場合は、この質問に賛成票を投じます。私はこの頭字語をソフトウェア文献で見たことがありません。ありがとう。
ジム・螺鈿

13
参考としてDCIのリンクを追加しました。
Nils Blum-Oeste

1
@JimRaden DCIはDataq、Context、interactionであり、MVC(Trygve Reenskauge)の父によって最初に策定されたパラダイムです。今では、この主題についてかなりの文献があります。コプリエンとビョルンビグの「リーンアーキテクチャ」
Rune FS

3
ありがとう。良くも悪くも、ほとんどの人は今ではオリジナルの文学についてさえ知りません。Googleによると、MVCに関する記事は5500万件ありますが、MCIとMVCについて言及している記事は250,000だけです。そして、Microsoft.comでは?7. AngularJS.orgはDCIの頭字語についても触れていません。「検索-site:angularjs.org dci-どのドキュメントにも一致しませんでした。」
ジムラーデン2013

リソースオブジェクトは、基本的にはAngular.jsのモデルです。それらを拡張しています。
Salman von Abbas

回答:


155

複数のコントローラーで何かを使用できるようにする場合は、サービスを使用する必要があります。これは簡単な不自然な例です:

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
}

23
モデルとしてプレーンなJavaScriptオブジェクトを作成し、これをコントローラースコープに割り当てるよりも、サービスを使用する利点は何でしょうか?
Nils Blum-Oeste

22
複数のコントローラー間で共有される同じロジックが必要な場合。また、この方法では、個別にテストするのが簡単になります。
Andrew Joslin

1
最後の例のようなものは、これはもっと理にかなっています。編集しました。
Andrew Joslin

9
ええ、プレーンな古いJavaScriptオブジェクトでは、AngularをListServiceに注入することはできません。この例のように、リストデータを最初に取得するために$ http.getが必要な場合、またはイベントを$ broadcastできるように$ rootScopeを注入する必要がある場合。
アンドリュージョスリン

1
この例をさらにDCIにするには、データをListServiceの外部に配置しないでください。
PiTheNumber

81

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

モデルオブジェクトが独自のプロパティで機能するメソッドを必要とする場合にのみ、このパターンを使用することに注意してください。私はすべてのサービスに対してこれを体系的に行うことを推奨していません

編集:私はこのパターンが「Angular model is plain old javascript object」のマントラに反するだろうと思っていましたが、このパターンは完全に問題ないように思えます。

編集(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ファクトリを使用、オブジェクト作成用に作成)

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

4
私はただAngularに入っていますが、退役軍人がこれが異端だと思うかどうか、なぜそう思うのか知りたいです。これはおそらく私が最初にそれに取り組む方法でもあります。誰かがいくつかのフィードバックを提供できますか?
Aaronius 2013

2
@Aaroniusは明確にする必要があります。angularjsのドキュメントやブログで「絶対にやらないでください」ということを実際に読んだことはありませんが、「angularjsはモデルを必要とせず、単純な古いjavascriptを使用しているだけです」のようなものは常に読んだことがあります。 、私は自分でこのパターンを発見する必要がありました。これはAngularJSでの私の最初の実際のプロジェクトなので、私はそれらの強力な警告を出して、人々が最初に考えることなくコピー/貼り付けをしないようにします。
ベンG

ほぼ同じパターンで解決しました。Angularが「クラシック」の意味でモデルを実際にサポートしていない(またはサポートしたいと思われている)モデルがないのは残念です。
drt 2013

3
それは私には異端のように見えません、あなたは彼らが作成された目的のためにファクトリを使用しています:オブジェクトの構築です。「angularjsにはモデルは必要ありません」というフレーズは、「特別なクラスから継承する必要がない、または特別なメソッド(ko.observableなどのノックアウト)を使用して、角度のあるモデルを操作する必要がないことを意味します。純粋なjsオブジェクトで十分です。」
フェリペカストロ2013年

1
コレクションごとに適切に名前が付けられたElementServiceがなければ、ほぼ同一のファイルがたくさんできませんか?
コリンアレン

29

Angularjsのドキュメントには次のように明記されています:

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

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

つまり、モデルの宣言方法はあなた次第です。これは単純なJavascriptオブジェクトです。

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


ドキュメントでこれが述べられている場所へのリンクを提供する必要があります。「Angularはモデルに制限や要件を課しません」というGoogle検索を実行しましたが、私の知る限り、公式ドキュメントのどこにも表示されません。

4
:それは古いangularjsドキュメント(1つの生きている間の応答)にあったgithub.com/gitsome/docular/blob/master/lib/angular/ngdocs/guide/...
SC

8

DCIはパラダイムであり、そのため、言語がDCIをサポートするか、サポートしないかのどちらかで、angularJSの方法はありません。ソース変換を使用する場合はJSがDCIを適切にサポートし、そうでない場合はいくつかの欠点があります。繰り返しになりますが、DCIは依存関係の注入とは関係なく、C#クラスはサービスであり、サービスでもありません。したがって、angulusJSでDCIを実行する最良の方法は、DCIをJSの方法で実行することです。これは、DCIが最初から定式化される方法にかなり近いものです。ソース変換を行わない限り、ロールメソッドはコンテキスト外でもオブジェクトの一部になるため、完全に変換することはできませんが、通常はメソッドインジェクションベースのDCIの問題です。あなたが見ればfullOO.infoDCIの信頼できるサイトでは、メソッドインジェクションも使用するルビの実装を確認できます。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);
   };
}

1
DCIについて詳しく説明していただきありがとうございます。それは素晴らしい読み物です。しかし、私の質問は、「モデルオブジェクトをangularjsのどこに配置するか」を本当に目的としています。DCIは参考のためにそこにあります。モデルがあるだけでなく、DCIの方法でそれを分割している可能性があります。質問を編集してより明確にします。
Nils Blum-Oeste 2012年

7

AngularJSのモデルに関するこの記事は、次のことに役立ちます。

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


7
リンクのみの回答は推奨されないことに注意してください。SOの回答は、ソリューションの検索のエンドポイントである必要があります。参照としてリンクを維持しながら、ここにスタンドアロンの概要を追加することを検討してください。
クレオパトラ2013

質問へのコメントにそのようなリンクを追加することは問題ありません。
ヨルボー2013

このリンクは実際には非常に優れた記事ですが、SOにふさわしい回答になるように細工する必要があります
Jeremy Zerr

5

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

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

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

私のチームと私はこのライブラリを積極的に開発し続けており、Angular開発者がこれに貢献し、バトルテストを行うのを楽しみにしています。


おい!これは本当に素晴らしいです!今すぐアプリに接続します。戦闘テストが始まりました。
J.ブルーニ

1
私はあなたの投稿を見ただけで、あなたngActiveResourceとAngularの$resourceサービスの違いは何か疑問に思っていました。私はAngularに少し慣れていないので、両方のドキュメントセットをすばやく閲覧しましたが、それらは多くの重複を提供しているようです。たngActiveResource前に開発された$resourceサービスが利用可能であること?
エリックB. 14

5

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

現在、Angularサービスは次世代のAngularを実現する数少ないコンセプトの1つであるように思われるため、すべてのロジックをサービスに移行するという一般的なガイドラインに従うことはおそらく賢明です。ただし、Angularサービスに直接依存していなくても分離モデルを作成できると私は主張します。必要な依存関係と責任のみを備えた自己完結型オブジェクトを作成することは、おそらく進むべき道です。また、自動化されたテストを行う際の作業も非常に簡単になります。最近では単一の責任が話題の仕事ですが、それは非常に理にかなっています!

これは、オブジェクトモデルをdomから分離するのに適したパターンの例です。

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

主要な目標は、ユニットテストからもビューからも簡単に使用できるようにコードを構造化することです。それを達成すれば、現実的で有用なテストを書くことができます。


4

私はこのブログ投稿でその正確な問題に取り組んでいます。

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

この投稿では、$ http$ resource、およびRestangularを使用した3つのアプローチについて説明しますます。

以下に、カスタムコードを使用したそれぞれのサンプルコードを示します。 getResult()は、Jobモデルのメソッドを使用した。

Restangular(easy peasy):

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は、誰もが同じページにアクセスできるようにする、データモデリングに対するより堅牢なソリューションを提供する可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.