Extend $resource with AngularJs


Almost every AngularJs applications need at one point to connect to an API to interact and retrieve datas. AngularJs out-of-the-box provide 2 commons ways to do so, using either $http or $resource object.

The latter wrap the former behind the scene to use in RESTful web API scenarios. Today, I’m gonna show you a little trick to extends $resource and give it a bit of extras convenient features.

$resource

In order to extend it, first we need to know how we generally use $resource. For the purpose of this article, we are going to assume that our application needs to play with users using the following API resource urls :

GET    /users
GET /users/:id
POST /users
PATCH /users/:id
DELETE /users/:id

A standard implementation of $resource for this particular case would be :

(function () {
'use strict';
  angular
.module('app.core.resources')
.factory('User', function ($resource) {
return $resource('/users/:id', { id: '@id' }, {
update: { method: 'PATCH' },
destroy: { method: 'DELETE' }
});
});
})();

Extending $resource

The example above works actually well at what it does, and there’s nothing wrong at all of doing it that way.

However, in my case, and to stay consistent between my Rails backend and my frontend, I wanted to be able to have an object that I can use that way :

// Create a new user
var user = new User({ lastname: 'Kent', firstname: 'Clark' });
user.save();
// Get all users
var users = User.all();
// Find a user
var user = User.find(22);
// Update a user
User.find(22, function (user) {
user.firstname = 'Martha';
user.save();
});
// Delete a user
User.find(22, function (user) {
user.delete();
});

As you can see, both new user and existsing user use the save() method to update or create.

The ActiveResource object

Let’s call our extension ActiveResource. Methods like save() and delete() need to be created at the prototype level in order to be used by object instance.

Also, save() need to check whether or not an id is already defined on the instance in order to execute a POST or a PATCH request. This service relies on https://github.com/iobaixas/angular-inflector in order to works.

The code can be found at https://gist.github.com/softmonkeyjapan/c250c5c6192b4fe03bc6

Explanation

Our ActiveResource object takes as a paremeter one argument that can be either an object or a string. In the case of a string, it should be the name of the resource in singular.

new ActiveResource('user');

The options will then be tranformed as :

{
url: '/users/:id/:action',
params: { id: '@id', action: '@action' },
namespace: 'user'
}

NOTE : The namespace option here is used to transform data before a POST or a PATCH request.

In order to add save() and delete() to our ActiveResource instance, we need to add those at the prototype level. What this means is that the reference of this inside those methods is actually the instance itself. This is important because in the case of a save(), we need to call the $update() method only if an id is present :

resource.prototype.save = function () {
var action = this.id ? '$update' : '$create';
return this[action]();
};

If you haven’t noticed, I also add instanceMethods(). It is a convenient method that can be use at the instanciation level to add custom mixins to an object instance. For example, in the case of a User :

(function () {
'use strict';
  angular
.module('app.core.resources')
.config(function (ARSettingsProvider) {
ARSettingsProvider.configure({
apiUrl: 'https://api.endpoint.com'
});
})
.service('User', function (ActiveResource) {
return new ActiveResource('user').instanceMethods({
isAdmin: function () {
return this.role == 'admin';
},
isUser: function () {
return this.role == 'user';
},
fullname: function () {
return this.lastname + ' ' + this.firstname;
}
});
});
})();

Those methods can then be used in any injectable object :

(function () {
'use strict';
  angular
.module('app.module.user')
.controller('UsersController', function (User) {
var vm = this;
User.find({ action: 'me' }, function (me) {
if (me.isAdmin()) {
vm.users = User.all();
}
});
});
})();

as well as within views :

<ul>
<li ng-repeat="user in users">
{{ user.fullname() }}
<div ng-if="user.isAdmin()">
<button class="btn btn-primary">Delete</button>
</div>
</li>
</ul>

Conclusion

This article only scratch the surface of what it is possible to do. It’s goal was to demonstrate how easy it is to use Angular native objects, and add functionnalities to them.