I have a model, defined using $resource
, that I am successfully loading.
Each loaded instance is, as promised, an instance of the class I defined.
(The example below is from the Angular docs. In it, User.get
results in an object that is an instanceof User
.)
var User = $resource('/user/:userId', {userId:'@id'});
However, imagine each User comes over the wire like this:
{
"username": "Bob",
"preferences": [
{
"id": 1,
"title": "foo",
"value": false
}
]
}
I defined a Preference
factory that adds valuable methods to Preference
objects. But when a User loads, those preferences
aren’t Preference
s, naturally.
I attempted this:
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
}
But it has no effect and never logs anything.
How can I make each item in my User
s’ preferences
array an instance of Preference
?
$resource is a simple implementation, and lacks in things like this.
User.prototype.constructor
won't do anything; angular doesn't try to act like it's object oriented, unlike other libraries. It's just javascript...But luckily, you have promises and javascript :-). Here's a way you could do it:
You could abstract this into a method which decorates any of a resource's methods: It takes an object, an array of method names, and a decorator function.
I was looking for a solution to the same problem as yours. I came up with the following approach.
This example is based on Offers instead of Users, as domain entity. Also, please note here's a trimmed down version of the whole thing, which in my case spans over some files:
Domain entity custom class:
Classic angular custom resource:
Custom repository, passed to controller by a service factory:
Most noticeable parts are:
Attempting to modify the constructor property of the prototype object won't do what you expect anyhow, please take a look at the very nice post here.
To really understand what is going on, one should look at the source code of the
ngResource
module - there are a lot of things at work there, but what's important is that the$resource
factory returns a plain JavaScript function (really, what else). Invoking this function with the documented parameters returns aResource
constructor object, which is defined privately inresourceFactory
.As you may recall, AngularJS services are singletons, meaning that calling
$resource
will return the same function every time (in this case,resourceFactory
). The important takeaway is that every time this function is evaluated, a newResource
constructor object is returned, meaning that you can prototype your own functions on it safely, without worrying that this will pollute allResource
instances globally.Here is a service that you can use much as the original
$resource
factory, while defining your own custom methods that will be available on all of its instances:Inside
myCustomFunction
you have access to the data returned from the server so you can usethis.preferences
and return whichever custom class you want to build.You can do this by overriding the built-in resource actions to transform the request and response (See transformRequest and transformResponse in the docs.):
transformResponse
does the job. Consider example (I wanted to use Autolinker to format response content).