我有一个使用$resource
定义的模型,我正在成功加载。
按照承诺,每个加载的实例都是我定义的类的实例。
(以下示例来自Angular文档。在其中,User.get
会生成一个instanceof User
的对象。)
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
s'preferences
数组中的每个项目设为Preference
的实例?
答案 0 :(得分:12)
$ resource是一个简单的实现,缺少这样的东西。
User.prototype.constructor
不会做任何事情;与其他库不同,angular不会尝试像面向对象那样行事。这只是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);
答案 1 :(得分: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
]
}
}
);
};
]);
答案 2 :(得分:3)
我一直在寻找与您相同问题的解决方案。我想出了以下方法 此示例基于Offers而不是Users,作为域实体。此外,请注意这里是整个东西的精简版本,在我的情况下跨越一些文件:
域实体自定义类:
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(/* .. */);
自定义存储库,由服务工厂传递给控制器:
function OfferRepository() {
// ...
}
angular.extend(OfferRepository.prototype, {
// ...
getById: function (offerId, success, error) {
var asyncResource = offerResource.get({
offerId: offerId
}, function (resource) {
asyncOffer.copyFromResource(resource);
(success || angular.noop)(asyncOffer);
}, function (response) {
(error || angular.noop)(response);
});
var asyncOffer = new offerModels.Offer(asyncResource);
return asyncOffer;
},
// ...
});
最值得注意的部分是:
答案 3 :(得分:2)
尝试修改原型对象的构造函数属性无论如何都不会达到预期效果,请查看非常好的帖子here。
要真正了解正在发生的事情,应该查看ngResource
模块的source code - 那里有很多工作要做,但重要的是{{1} } factory返回一个普通的JavaScript函数(真的,还有什么)。使用记录的参数调用此函数将返回$resource
构造函数对象,该对象在Resource
中私有定义。
您可能还记得,AngularJS服务是单例,这意味着每次调用resourceFactory
都会返回相同的函数(在本例中为$resource
)。重要的是,每次评估此函数时,都会返回一个新的resourceFactory
构造函数对象,这意味着您可以安全地在其上构建自己的函数原型,而不必担心这会污染全局的所有Resource
实例
这是一项服务,您可以将其用作原始Resource
工厂,同时定义可在其所有实例上使用的自定义方法:
$resource
在angular.module('app').factory('ExtendedResourceFactory', ['$resource',
function($resource) {
function ExtendedResourceFactory() {
var Resource = $resource.apply(this, arguments);
Resource.prototype.myCustomFunction = function() {
...
};
return Resource;
}
return ExtendedResourceFactory;
}
]);
内,您可以访问从服务器返回的数据,以便您可以使用myCustomFunction
并返回您要构建的自定义类。
答案 4 :(得分:1)
transformResponse
完成这项工作。考虑示例(我想使用Autolinker格式化响应内容)。
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'
} });