2013-05-09 14 views
15

$resourceを使って定義したモデルがあり、正常に読み込んでいます。

ロードされた各インスタンスは、約束どおり、私が定義したクラスのインスタンスです。

(以下の例では、角度のドキュメントからのものであることで、instanceof Userであるオブジェクト内User.get結果。)

var User = $resource('/user/:userId', {userId:'@id'}); 

しかしながら、各ユーザは、このようなワイヤ上来る想像:

{ 
    "username": "Bob", 
    "preferences": [ 
    { 
     "id": 1, 
     "title": "foo", 
     "value": false 
    } 
    ] 
} 

Preferenceオブジェクトに貴重なメソッドを追加するPreferenceファクトリを定義しました。しかし、ユーザーが読み込んだ場合、preferencesは当然Preferenceではありません。

私はこの試みた:

User.prototype.constructor = function(obj) { 
    _.extend(this, obj); 
    this.items = _.map(this.preferences, function(pref) { 
    return new Preference(pref); 
    }); 
    console.log('Our constructor ran'); // never logs anything 
} 

をしかし、それは効果がありませんし、決して何かをログに記録します。

User 'preferencesアレイの各項目をPreferenceのインスタンスにするにはどうすればよいですか?

+0

Angularの外に物をロードしてから、 'preferences'の世話をした後に、ロードされた各ユーザの'新しいユーザ '自身を作成する必要がありますか? –

+0

あなたのリソースでレスポンスインターセプタを使用し、必要に応じてレスポンスを変更できると思います。興味があれば、私は同様のことを達成するコードを持っています。 – teone

答えて

12

$リソースは単純な実装であり、このようなものに欠けています。

User.prototype.constructorは何も行いません。角度は、他のライブラリとは異なり、オブジェクト指向のように動作しようとしません。それはただのJavaScriptです。

..でも、幸いなことに、あなたは約束とjavascript :-)を持っています。ここではあなたがそれを行うことができ方法です:

function wrapPreferences(user) { 
    user.preferences = _.map(user.preferences, function(p) { 
    return new Preference(p); 
    }); 
    return user; 
} 

var get = User.get; 
User.get = function() { 
    return get.apply(User, arguments).$then(wrapPreferences); 
}; 
var $get = User.prototype.$get; 
User.prototype.$get = function() { 
    return $get.apply(this, arguments).$then(wrapPreferences); 
}; 

あなたは可能性があり、リソースのメソッドのいずれかを飾る方法にこの抽象:それは、オブジェクト、メソッド名の配列、およびデコレータ機能をとります。

function decorateResource(Resource, methodNames, decorator) { 
    _.forEach(methodNames, function(methodName) { 
    var method = Resource[methodName]; 
    Resource[methodName] = function() { 
     return method.apply(Resource, arguments).$then(decorator); 
    }; 
    var $method = Resource.prototype[methodName]; 
    Resource.prototype[methodName] = function() { 
     return $method.apply(this, arguments).$then(decorator); 
    }; 
    }); 
} 
decorateResource(User, ['get', 'query'], wrapPreferences); 
+0

私は関数の最下部に 'var methodName'があると思います。おそらく、' var method'を意味し、 'Resource [methodName]'の次の行にあります。 – tjb1982

+0

ありがとう、編集:-) –

+0

良い説明。おそらく 'decorateResource'宣言とその呼び出しの間に、パラメータ順にミスタイプがあります。 – superjos

3

私はあなたと同じ問題に対する解決策を探していました。私は次のアプローチを思いついた。
この例は、ドメインエンティティとしてのユーザーの代わりにオファーに基づいています。また、ここで注意私の場合には、いくつかのファイルにまたがる全部、のトリムダウンバージョンですください

ドメインエンティティのカスタムクラス:

function Offer(resource) { 
    // Class constructor function 
    // ... 
} 

angular.extend(Offer.prototype, { 
    // ... 

    _init: function (resource) { 
     this._initAsEmpty(); 

     if (typeof resource == 'undefined') { 
      // no resource passed, leave empty 
     } 
     else { 
      // resource passed, copy offer from that 
      this.copyFromResource(resource); 
     } 
    }, 

    copyFromResource: function (resource) { 
     angular.extend(this, resource); 
     // possibly some more logic to copy deep references 
    }, 

    // ... 
}); 

クラシック角度カスタムリソース:

var offerResource = $resource(/* .. */); 

サービス工場によってコントローラに渡されるカスタムリポジトリ:

個の最も目立つ部分です:

  • カスタムエンティティクラス、それは例えば、おそらく深いコピー機能を持つ(リソースインスタンスから始まる埋める/自分自身を構築することができますオファーのポジション)
  • リソースをラップするカスタムリポジトリクラス。これは古典的な非同期リソースの回答を返しませんが、代わりにカスタムのエンティティインスタンスを返し、後でそのインスタンスをロードしたばかりのリソースで満たします。
5

あなたは組み込みのリクエストとレスポンス変換するためのリソースアクションオーバーライドすることによってこれを行うことができます(transformRequest and transformResponse in the docsを参照する。):

var m = angular.module('my-app.resources'); 
m.factory('User', [ 
      '$resource', 
    function($resource) { 

    function transformUserFromServer(user) { 
     // Pass Preference directly to map since, in your example, it takes a JSON preference as an argument 
     user.preferences = _.map(user.preferences, Preference); 
     return user; 
    } 

    function transformUserForServer(user) { 
     // Make a copy so that you don't make your existing object invalid 
     // E.g., changes here may invalidate your model for its form, 
     // resulting in flashes of error messages while the request is 
     // running and before you transfer to a new page 
     var copy = angular.copy(user); 
     copy.preferences = _.map(user.preferences, function(pref) { 
     // This may be unnecessary in your case, if your Preference model is acceptable in JSON format for your server 
     return { 
      id: pref.id, 
      title: pref.title, 
      value: pref.value 
     }; 
     }); 

     return copy; 
    } 

    function transformUsersFromServer(users) { 
     return _.map(users, transformUserFromServer); 
    } 

    return $resource('/user/:userId', { 
     userId: '@id' 
     }, { 
     get: { 
      method: 'GET', 
      transformRequest: [ 
      angular.fromJson, 
      transformUserFromServer 
      ] 
     }, 
     query: { 
      method: 'GET', 
      isArray: true, 
      transformRequest: [ 
      angular.fromJson, 
      transformUsersFromServer 
      ] 
     }, 
     save: { 
      method: 'POST', 
      // This may be unnecessary in your case, if your Preference model is acceptable in JSON format for your server 
      transformRequest: [ 
      transformUserForServer, 
      angular.toJson 
      ], 
      // But you'll probably still want to transform the response 
      transformResponse: [ 
      angular.fromJson, 
      transformUserFromServer 
      ] 
     }, 
     // update is not a built-in $resource method, but we use it so that our URLs are more RESTful 
     update: { 
      method: 'PUT', 
      // Same comments above apply in the update case. 
      transformRequest: [ 
      transformUserForServer, 
      angular.toJson 
      ], 
      transformResponse: [ 
      angular.fromJson, 
      transformUserFromServer 
      ] 
     } 
     } 
    ); 
    }; 
]); 
1

transformResponseは仕事をしていません。例を考えてみましょう(私はAutolinkerを使用してレスポンスコンテンツをフォーマットしたがっています)。あなたはとにかく期待しないだろうプロトタイプオブジェクトのconstructorプロパティを変更しようと

return $resource('posts/:postId', { 
    postId: '@_id' 
}, { 
    get : { 
     transformResponse : function(data) { 
      var response = angular.fromJson(data); 
      response.content = Autolinker.link(response.content); 
      return response; 
     } 
    }, 
    update: { 
     method: 'PUT' 
} }); 
2

、とても素敵なポストhereをご覧ください。本当に何が起こっているかを理解するには

、1はngResourceモジュールのsource codeを見なければならない - そこに多くのことが仕事であるが、何が重要ですが$resource工場ではJavaScriptの関数(実際に平野を返すことで、どのようなelse)。文書化されたパラメータでこの関数を呼び出すとResourceコンストラクタオブジェクトが返されます。これはresourceFactoryに限定されています。

AngularJSサービスはシングルトンです。つまり、$resourceを呼び出すと毎回同じ機能が返されます(この場合はresourceFactory)。この関数が評価されるたびに、新しいResourceコンストラクタオブジェクトが返されます。つまり、すべてのResourceインスタンスをグローバルに汚染することを心配することなく、安全に独自の関数をプロトタイプ作成できます。ここで

そのすべてのインスタンスで利用できるようになり、独自のカスタムメソッドを定義している間、あなたは元$resource工場として多くを使用できるサービスです。myCustomFunctionインサイド

angular.module('app').factory('ExtendedResourceFactory', ['$resource', 
    function($resource) {               
    function ExtendedResourceFactory() { 
     var Resource = $resource.apply(this, arguments); 

     Resource.prototype.myCustomFunction = function() { 
     ... 
     }; 

     return Resource; 
    } 

    return ExtendedResourceFactory; 
    } 
]); 

サーバーから返されたデータにアクセスできるので、this.preferencesを使用して、作成するカスタムクラスを返すことができます。

+0

これは私のためにはうまくいかなかったか、何か逃した。私は非常に関連している新しい質問を追加しましたhttp://stackoverflow.com/questions/36921494/how-can-i-get-the-url-from-an-ng-resource – alfthan