Restangular on Angular

Although Angular on its own is powerful enough to build standalone applications where we pack all important data inside the application, in doing so we would be missing out on one of the nicest features of the framework: its ability to talk with the outside world.

In this section, we’re going to talk specifically about an incredibly well-developed and well-thought-out library: Restangular. All of the code in this article is available at the end of the article.

The what and the why

Restangular is an Angular service specifically designed simply to fetch data from the rest of the world.

Why not use $http or $resource? Although $http and $resource are built into the framework, they carry limitations with them. Restangular takes a completely different approach to XHR and makes it a pleasant experience.

The complete list of benefits to using Restangular is available on the README, but we’ll cover a few benefits here:

Using promises makes Restangular feel more Angular-esque as it uses and resolves on promises. This enables us to chain together responses just as though we’re using the raw $http method.

Restangular has little to no magic included in the library. We don’t have to guess how it works or dig up documentation on how to use it.

All HTTP methods are supported.

While $resource requires us to specify the URLs we want to fetch, Restangular doesn’t require us to know the URLs in advance, nor do we have to specify them all upfront, other than the base URL.

Want to use nested resources? No need to create another instance of the Restangular instance, it’ll handle it for us.

Unlike $resource, we only ever need to create one instance of the Restangular resource object.

And there is much, much more.

Installation

Installing Restangular is easy – we have options. We can download the files manually (from GitHub) and include the file locally. If we download the file to our js/vendor directory, then we can include it in our HTML, like so:

1
<script type="test/javascript" src="js/vendor/restangular.min.js"></script>

We can include the JavaScript libraries hosted on jsDelivr in our page:

1
2
<script type="text/javascript" src="http://cdn.jsdelivr.net/restangular/latest/restangular.js"></script> <script type="text/javascript" src="http://cdn.jsdelivr.net/restangular/latest/restangular.min.js"></script>

Alternatively, we can use npm to install Restangular if we’re using npm in our project:

1
$ npm install restangular

Or we can use Bower to install Restangular, if we’ve set up Bower for our project.

1
$ bower install restangular

Note: Restangular depends on either lodash or underscore, so in order support restangular, we’ll need to make sure we include one of the two.

We can either use jsDelivr to include lodash:

1
<script type="text/javascript" src="//cdn.jsdelivr.net/lodash/2.1.0/lodash.compat.min.js"></script>

Or download lodash here or use bower to install.

1
bower install lodash

And include it as a script on the page:

1
<script type="text/javascript" src="/js/vendor/lodash/dist/lodash.min.js"></script>

Also, just like any other AngularJS library, we’ll need to include the restangular resource as a dependency for our app module object:

1
angular.module('myApp', ['restangular']);

Once we’ve done that, we’ll be able to inject the Restangular service in our Angular objects:

1
2
3
4
5
angular.module('myApp') .controller('MainController', ['$scope', 'Restangular', function($scope, Restangular) { }]);

Intro to the Restangular object

To use Restangular, there are two ways we can create an object to fetch services. We can either set the base route to fetch objects from:

1
var User = Restangular.all('users');

This will set all HTTP requests that come from the Restangular service to pull from /users. For instance, calling getList() on the above object will fetch from /users:

1
2
var allUsers = User.getList(); // GET /users

It’s also possible to fetch nested requests for a single object. Instead of passing only a route, we can pass a unique ID to pull from as well:

1
var oneUser = Restangular.one('users', 'abc123');

This will generate the request /users/abc123 when calling getList() on it.

1
2
3
4
oneUser.get().then(function(user) { // GET /users/abc123/inboxes user.getList('inboxes'); });

As you can see from above, Restangular is smart enough to figure out how to construct URLs based upon the methods that we are calling on the Restangular source object. Sometimes, however, it is convenient to set the URL that we’re fetching, especially if our back end doesn’t support pure RESTful APIs.

To set the URL for a specific request, we can pass a separate argument using the allUrl method:

1
2
3
4
5
6
// All URLs on searches will use `http://google.com/` as // the baseUrl var searches = Restangular.allUrl('searches', 'http://google.com/'); // Will send a request to GET http://google.com/ searches.getList();

Additionally, we can set the base URL for one particular request, rather than manipulating the entire request using oneUrl:

1
2
3
4
5
var singleSearch = Restangular.oneUrl('betaSearch', 'http://beta.google.com/1'); // Trigger a request to GET http://google.com/1 singleSearch.get();

Using Restangular

With a good handle on the Restangular object, we can now get down to using it to make requests.

With the initial object that’s returned from Restangular, we have a lot of methods that we can use to interact with our back-end API.

Let’s say we’ve created a Restangular object that represents public discussions:

1
var messages = Restangular.all('messages');

With this object, we can get a list of all of the messages with the getList() method. This getList() method returns a collection containing methods we can call to work with the specific collection.

1
2
3
// allMessages is a promise that will resolve into the // list of all messages var allMessages = messages.getList();

See it

  • {{ msg.body }}
These messages are being fetched out of a mongo instance

We can also create messages using the Restangular object. To create an object, we’ll use the post() method.

The post method requires a single object as a parameter and will send a (you guessed it) POST request to the URL we specified. We can also add queryParameters and headers to this request.

1
2
3
4
5
6
7
8
9
// POST to /messages var newMessage = { body: "Hello world" }; messages.post(newMessage); // OR we can call this on an element to create a nested // resource for the element var message = Restangular.one('messages', 'abc123'); message.post('replies', newMessage);

Because Restangular returns promises, we can then call methods on the returned data on promises so that we can run a function after the promise has completed. For instance, after we update a collection, we can then refresh the collection on our scope:

1
2
3
4
5
messages.post(newMessage).then(function(newMsg) { $scope.messages = messages.getList(); }, function error(reason) { // An error has occurred });

Try it

  • {{ msg.body }}
These messages are being fetched out of a mongo instance

We can also remove an object from the collection. Using the remove() method, we can send a DELETE HTTP request to our back end. To send a delete request, we can call the remove() method on an object inside the collection (an element).

1
2
3
var message = messages.get(123); message.remove(); // Send a DELETE to /messages

Try it

  • X {{ msg.body }}
These messages are being fetched out of a mongo instance

Updating and saving objects is something we’ll do quite often. Traditionally, this operation is done with the HTTP method PUT. Restangular supports this out of the box through the method put().

To update an object, we’ll need to query the object, set our new attributes on the instance, and then call put() on the object to save the updated attributes in the back end.

Note, that before modifying an object, it’s good practice to copy it and then modify the copied object before we save it. Restangular has it’s own version of copy such that it won’t rebind this in the bundled functions. It’s good practice to use Restangular.copy() when updating an object.

Try it

  • Edit {{ msg.body }}
Save

These messages are being fetched out of a mongo instance

Note that before modifying an object, it’s good practice to copy it and then modify the copied object before we save it. Restangular has its own version of copy such that it won’t rebind this in the bundled functions. It’s good practice to use Restangular.copy() when updating an object.

Now that we have experience working on instances of our collection, let’s dig into nested components. Nested components are those that live underneath other components. For instance, for all of the books written by a certain author.

Restangular supports nested resources by default. In fact, we can query a particular instance from our collection for their nested resources.

1
2
3
var author = Restangular.one('users', 'abc123'); // Builds a GET to /authors/abc123/books var books = author.getList('books');

Try it

Get all my messages (based off a custom user id, generated for you)

In order to fetch your articles, you need to add at least one. Try adding one now…

  • {{ msg.body }}

These messages are being fetched out of a mongo instance

But what about my HTTP methods?

Restangular supports, out of the box, all HTTP methods. It can support calling GET, PUT, POST, DELETE, HEAD, TRACE, OPTIONS, and PATCH.

1
2
3
4
5
6
7
8
9
10
author.get(); // GET /authors/abc123 author.getList('books'); // GET /authors/abc123/books author.put(); // PUT /authors/abc123 author.post(); // POST /authors/abc123 author.remove(); // DELETE /authors/abc123 author.head(); // HEAD /authors/abc123 author.trace(); // TRACE /authors/abc123 author.options(); // OPTIONS /authors/abc123 author.patch(); // PATCH /author/abc123

Restangular also makes it possible to create custom HTTP methods for cases when our back-end server maps resources differently than we expect.

For instance, if we want to get the author’s biography (not a RESTful resource), then we can set the URL through the customMETHOD() function (where METHOD is replaced by any of the following: GET, GETLIST, DELETE, POST, PUT, HEAD, OPTIONS, PATCH, TRACE).

1
2
3
4
5
6
7
8
9
10
11
// Maps to GET /users/abc123/biography author.customGET("biography"); // Or customPOST with a new bio object // as {body: "Ari's bio"} // The two empty fields in between are the // params field and any custom headers author.customPOST({body: "Ari's Bio"}, // post body "biography", // route {}, // custom params {}); // custom headers

Custom query parameters and headers

With all of these methods, we can send custom query parameters or custom headers.

To add custom query parameters, we’ll add a JavaScript object as the second parameter to our method call. We can also add a second JavaScript object as a third parameter. Most all of the individual methods that we can call on an element take these two parameters as optional parameters.

With custom query parameters, a post method might look something like:

1
2
3
4
var queryParamObj = { role: 'admin' }, headerObj = { 'x-user': 'admin' }; messages.getList('accounts', queryParamObj, headerObj);

Restangular is incredibly simple to use and gets out of the way so that we can focus on building our app, rather than wrestling with the API.

Configuring Restangular

Restangular is highly configurable and expects us to configure it for our apps. It does come with defaults for every single property, so we don’t have to configure it if we don’t need to do so.

There are a few different places where we can configure a Restangular service. We can configure it globally or using a custom service.

To configure Restangular for all Restangular usages, regardless of the location it’s used in, we can inject the RestangularProvider in a config() function or inject Restangular in a run() function.

A good rule of thumb to determine where we should configure our Restangular instances: If we need to use any other service in configuring Restangular, then we should configure it in the run() method, otherwise we’ll keep it in the config() method.

Setting the baseUrl

To set the baseUrl for all calls to our backendAPI, we can use the setBaseUrl() method. For instance, if our API is located at /api/v1, rather than at the root of our server.

1
2
3
4
angular.module('myApp') .config(function(RestangularProvider) { RestangularProvider.setBaseUrl('/api/v1'); });

Adding element transformations

We can add any element transformations after an element has been loaded by Restangular.

Using these elementTransformers, we can add custom methods to our Restangular objects, such as when the object instance was fetched, for example.

This will get called as a callback that will enable us to update or modify the element we fetched after it’s been loaded, but before we use it in our Angular objects.

1
2
3
4
5
6
7
8
9
10
11
12
angular.module('myApp') .config(function(RestangularProvider) { // Three parameters: // the route // if it's a collection - boolean (true/false) // and the transformer RestangularProvider.addElementTransformer('authors', false, function(element) { element.fetchedAt = new Date(); return element; }); });

Setting responseInterceptors

Restangular can set responseInterceptors. responseInterceptors are useful for when we want to translate the response we get back from the server. For instance, if our server comes back with the data tucked away in a nested object, we can use a responseInterceptor to dig it out.

This is called after every response we get back from the backend. It will get called with the following parameters:

1
2
3
4
5
6
7
8
9
10
angular.module('myApp') .config(function(RestangularProvider) { RestangularProvider.setResponseInterceptor( function(data, operation, what) { if (operation == 'getList') { return data[what]; } return data; }); });

Using requestInterceptors

Restangular supports the other side of the operation as well: We can work with the data we are going to send to the server before we ever actually send any data back to the server in the first place.

requestInterceptors are useful for times when we’ll need to run manipulations on the object before sending it to the server. For instance, we can’t call directly to mongo with an _id field, so we have to remove it before it gets sent to the backend if we’re in a PUT operation.

To set a requestInterceptor, we can use the method setRequestInterceptor(). The setRequestInterceptor() method will be called with the following parameters:

1
2
3
4
5
6
7
8
9
10
11
angular.module('myApp') .config(function(RestangularProvider) { RestangularProvider.setRequestInterceptor( function(elem, operation, what) { if (operation === 'put') { elem._id = undefined; return elem; } return elem; }); });

Custom fields

Restangular also supports setting custom Restangular fields. This is important for times when we are connecting not to a back-end server, but to a back-end database, such as MongoDB where the id field doesn’t map to an id.

For instance, when connecting to MongoDB, the id field actually maps to _id.$oid.

1
2
3
4
5
6
7
angular.module('myApp') .config(function(RestangularProvider) { RestangularProvider.setRestangularFields({ id: '_id.$oid' }); }); });

Catching errors with errorInterceptors

It’s also possible to set error Interceptors, for those times when we want to catch an error from within Restangular. Using the errorInterceptor gives us the ability to halt the flow of the error down to our app.

If we return false from the errorInterceptor, then the flow of the promise will end and our app will never need to deal with handling errors.

This is a good time to handle dealing with authentication failures, for instance. If any request comes back with a 401, we can use the errorInterceptor to catch it and handle redirecting the user to the login page.

1
2
3
4
5
6
7
8
9
angular.module('myApp') .config(function(RestangularProvider) { RestangularProvider.setErrorInterceptor( function(resp) { displayError(); return false; // stop the promise chain }); }); });

Configuring Restangular to talk to MongoDB

One of the nicer features of using Restangular is that we can connect our app to talk directly to a MongoDB database (in fact, all the examples on this page talk directly to a MongoDB database).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
angular.module('myApp', ['restangular']) .constant('apiKey', '123412341234') .config(function(RestangularProvider, apiKey) { RestangularProvider.setBaseUrl( 'https://api.mongolab.com/api/1/databases/YOURDB/collections'); RestangularProvider.setDefaultRequestParams({ apiKey: apiKey }) RestangularProvider.setRestangularFields({ id: '_id.$oid' }); RestangularProvider.setRequestInterceptor( function(elem, operation, what) { if (operation === 'put') { elem._id = undefined; return elem; } return elem; }) });

Custom Restangular services

Finally, we highly recommend using Restangular from inside a custom service object. This is particularly useful, as we can configure Restangular on a per-service level using a service as well as disconnecting the logic to talk to our back end from within our controllers/directives and enabling our services to handle talking to them directly.

This disconnection from inside Angular objects also helps with making testing a breeze as we can stub and mock back-end calls without worrying about actually making the calls to our back end during tests.

To create a service that wraps Restangular, we simply need to inject the Restangular service in our factory and call the methods like normal. Inside this factory, we can create custom configurations by using the withConfig() function.

For instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
angular.module('myApp', ['restangular']) .factory('MessageService', [ 'Restangular', function(Restangular) { var restAngular = Restangular.withConfig(function(Configurer) { Configurer.setBaseUrl('/api/v2/messages'); }); var _messageService = restAngular.all('messages'); return { getMessages: function() { return _messageService.getList(); } } }]);

Restangular is really, really, really cool and reduces a lot of the complexity and unknowns from using the $resource service and let’s us focus on the building the functionality of our web app.

All of the code for this article can be found at: http://d.pr/sb9f.

There are many more configuration operations we can set on Restangular. We encourage you to check out the Restangular documentation or check out our upcoming book that covers an extensive review of Restangular.

Get the weekly email all focused on AngularJS. Sign up below to receive the weekly email and exclusive content.
We will never send you spam and it's a cinch to unsubscribe.

Download a free sample of the ng-book: The Complete Book on AngularJS

ng-book: The Complete Book on AngularJS is the canonical AngularJS book available today.

It's free, so just enter your email address and the PDF will be sent directly to your inbox. Mailchimp can take up to an hour to deliver the free sample chapter, but if you don't receive it within the hour, send us an email and we'll manually send them to you!

We'll send you updates about the book, when it updates and other free content.

We will never send you spam and it's a cinch to unsubscribe.

Comments

comments powered by Disqus