Virtual attributes in ngResource
Asked Answered
L

4

7

Is it possible to add virtual attributes to a ngResource? I created a service like this

app.factory('Person', ['$resource', function($resource) {

  return $resource('api/path/:personId', {
    personId: '@_id'
      }, {
        update: {
          method: 'PUT'
        }
      });
}])

Person has an attribute name and an attribute surname.
I want to retrive the fullname by adding a virtual attribute fullname returning resource.name + resource surname.
I know I could add it in the controller, but adding it to the service it will make it much more portable. I tried something like this

app.factory('Person', ['$resource', function($resource) {

  return $resource('api/path/:personId', {
    personId: '@_id'
      }, {
        update: {
          method: 'PUT'
        },
    fullname: function(resource){
      return resource.name + ' ' + resource.surname;
    }
  });
 });
}])

but it doesn't work.

Lovellalovelock answered 4/6, 2014 at 13:2 Comment(0)
A
4

You could try intercepting the response from the Person resource and augment the response. Like this:

app.factory('Person', ['$resource', function($resource) {
  function getFullName(){
      return this.name + ' ' + this.surname;
  };

  return $resource('api/path/:personId', {
    personId: '@_id'
      }, {
        update: {
          method: 'PUT'
        },
        'get': {method:'GET', isArray:false,interceptor:{
              'response': function(response) {
                  response.fullname = getFullName; //augment the response with our function
                  return response;
               }
         }}
      });
}]);
Appear answered 13/6, 2014 at 13:20 Comment(0)
H
4

As per the docs, $resource returns a constructor. You can leverage the usual prototype of the constructor to add members, i.e.:

var Person = $resource('api/path/:personId', {
            personId: '@_id'
        }, {
            update: { method: 'PUT' }
        }); // same as the 1st snippet from the question

Person.prototype.xxxx = ...;

return Person; // from your Angular service

In the previous example xxxx can be anything normally allowed in a prototype. If what you want is actually a derived property, i.e. a JS property that will always reflect the name and surname properties, then you need a fairly recent browser that supports Object.defineProperty() and replace the Person.prototype line above with:

Object.defineProperty(
    Person.prototype,
    "fullname",
    {get: function() { return this.name + " " + this.surname; }}
);
Housemother answered 10/6, 2014 at 10:25 Comment(5)
It works like any other property but you can't set a value to it (unless you specify a setter)Econah
Of course not, and it should not, since this is a derived (calculated) property. A setter can be defined if needed by some other use case though.Housemother
I'm not disagreeing with you ^^, just wanted to clarify.Econah
This looks good, but I want to see if someone has a more "angular" solution.Lovellalovelock
The angular way is to transform the response as done in that plunk: jsfiddle.net/roadprophet/YpvG7Lathrope
A
4

You could try intercepting the response from the Person resource and augment the response. Like this:

app.factory('Person', ['$resource', function($resource) {
  function getFullName(){
      return this.name + ' ' + this.surname;
  };

  return $resource('api/path/:personId', {
    personId: '@_id'
      }, {
        update: {
          method: 'PUT'
        },
        'get': {method:'GET', isArray:false,interceptor:{
              'response': function(response) {
                  response.fullname = getFullName; //augment the response with our function
                  return response;
               }
         }}
      });
}]);
Appear answered 13/6, 2014 at 13:20 Comment(0)
P
2

If you only need the derived property for display purposes, you can create a display filter rather than loading up your model with redundant data:

app.filter('personFullName', function() {
  return function(person) {
    return person.name + " " + person.surname;
  };
})

Then reference the filter in your template:

<div>
  <p>{{person | personFullName}}</p>
  <p>- should match -</p>
  <p>{{person.name}} {{person.surname}}</p>
</div>
Phile answered 12/6, 2014 at 0:7 Comment(1)
This solution is interesting, but I think I would prefer to keep the virtual attribute into the model, so that I can access it in several views without having to deal with the filter in each of them.Lovellalovelock
B
1

Khanh TO put me on the way to a working answer for me, but, as explained in this Angular ticket, in the interceptor, you have to handle and return response.resource instead of response.

Here is a solution (working for Angular 1.4):

app.factory('Person', ['$resource', function($resource) {

return $resource(
    'api/path/:personId',
    {personId: '@_id'},
    {
        update: {method: 'PUT'},
        get: {
            method: 'GET',
            isArray: false,
            interceptor: {
                response: function(response) {
                    var resource = response.resource;
                    resource.fullname = resource.name + ' ' + resource.surname;
                    return resource;
                }
            }
        }
    });
}]);
Barbrabarbuda answered 14/1, 2016 at 14:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.