mgonto / restangular

AngularJS service to handle Rest API Restful Resources properly and easily
MIT License
7.87k stars 840 forks source link

Restangular

Build Status Coverage Status David Known Vulnerabilities PayPayl donate button Donate on Gittip

Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. It's a perfect fit for any WebApp that consumes data from a RESTful API.

Note This version of Restangular only supports Angular 1. For an Angular 2+ version of Restangular, check out ngx-restangular. It's a separate project with different maintainers, so issues regarding ngx-restangular should be reported over there :wink:

Learn Restangular! Try the live demo on plunkr. It uses the same example as the official Angular Javascript Project, but with Restangular! Or watch a video introduction of a talk I gave at Devoxx France about Restangular.

Table of contents

Back to top

Differences with $resource

Restangular has several features that distinguish it from $resource:

Let's see a quick and short example of these features

// Restangular returns promises
Restangular.all('users').getList()  // GET: /users
.then(function(users) {
  // returns a list of users
  $scope.user = users[0]; // first Restangular obj in list: { id: 123 }
})

// Later in the code...

// Restangular objects are self-aware and know how to make their own RESTful requests
$scope.user.getList('cars');  // GET: /users/123/cars

// You can also use your own custom methods on Restangular objects
$scope.user.sendMessage();  // POST: /users/123/sendMessage

// Chain methods together to easily build complex requests
$scope.user.one('messages', 123).one('from', 123).getList('unread');
// GET: /users/123/messages/123/from/123/unread

Back to top

How do I add this to my project?

You can download this by:

<!-- Or use TAG number for specific version. New versions are auto deployed -->
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/restangular/1.5.1/restangular.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/restangular/1.5.1/restangular.min.js"></script>

Back to top

Dependencies

Restangular depends on Angular and Lodash (or Underscore).

Back to top

Production apps using Restangular

Each time, there're more Production WebApps using Restangular. If your webapp uses it and it's not in the list, please create an issue or submit a PR:

Back to top

Starter Guide

Quick Configuration (For Lazy Readers)

This is all you need to start using all the basic Restangular features.

// Add Restangular as a dependency to your app
angular.module('your-app', ['restangular']);

// Inject Restangular into your controller
angular.module('your-app').controller('MainCtrl', function($scope, Restangular) {
  // ...
});

The Restangular service may be injected into any Controller or Directive :) Note: When adding Restangular as a dependency it is not capitalized 'restangular' But when injected into your controller it is 'Restangular'

Back to top

Using Restangular

Creating Main Restangular object

There are 3 ways of creating a main Restangular object. The first one and most common one is by stating the main route of all requests. The second one is by stating the main route and object of all requests.

// Only stating main route
Restangular.all('accounts')

// Stating main object
Restangular.one('accounts', 1234)

// Gets a list of all of those accounts
Restangular.several('accounts', 1234, 123, 12345);

Back to top

Let's code!

Now that we have our main Object let's start playing with it.

// First way of creating a Restangular object. Just saying the base URL
var baseAccounts = Restangular.all('accounts');

// This will query /accounts and return a promise.
baseAccounts.getList().then(function(accounts) {
  $scope.allAccounts = accounts;
});

// Does a GET to /accounts
// Returns an empty array by default. Once a value is returned from the server
// that array is filled with those values. So you can use this in your template
$scope.accounts = Restangular.all('accounts').getList().$object;

var newAccount = {name: "Gonto's account"};

// POST /accounts
baseAccounts.post(newAccount);

// GET to http://www.google.com/ You set the URL in this case
Restangular.allUrl('googlers', 'http://www.google.com/').getList();

// GET to http://www.google.com/1 You set the URL in this case
Restangular.oneUrl('googlers', 'http://www.google.com/1').get();

// You can do RequestLess "connections" if you need as well

// Just ONE GET to /accounts/123/buildings/456
Restangular.one('accounts', 123).one('buildings', 456).get()

// Just ONE GET to /accounts/123/buildings
Restangular.one('accounts', 123).getList('buildings')

// Here we use Promises then
// GET /accounts
baseAccounts.getList().then(function (accounts) {
  // Here we can continue fetching the tree :).

  var firstAccount = accounts[0];
  // This will query /accounts/123/buildings considering 123 is the id of the firstAccount
  $scope.buildings = firstAccount.getList("buildings");

  // GET /accounts/123/places?query=param with request header: x-user:mgonto
  $scope.loggedInPlaces = firstAccount.getList("places", {query: 'param'}, {x-user: 'mgonto'})

  // This is a regular JS object, we can change anything we want :)
  firstAccount.name = "Gonto"

  // If we wanted to keep the original as it is, we can copy it to a new element
  var editFirstAccount = Restangular.copy(firstAccount);
  editFirstAccount.name = "New Name";

  // PUT /accounts/123. The name of this account will be changed from now on
  firstAccount.put();
  editFirstAccount.put();

  // PUT /accounts/123. Save will do POST or PUT accordingly
  firstAccount.save();

  // DELETE /accounts/123 We don't have first account anymore :(
  firstAccount.remove();

  var myBuilding = {
    name: "Gonto's Building",
    place: "Argentina"
  };

  // POST /accounts/123/buildings with MyBuilding information
  firstAccount.post("Buildings", myBuilding).then(function() {
    console.log("Object saved OK");
  }, function() {
    console.log("There was an error saving");
  });

  // GET /accounts/123/users?query=params
  firstAccount.getList("users", {query: params}).then(function(users) {
    // Instead of posting nested element, a collection can post to itself
    // POST /accounts/123/users
    users.post({userName: 'unknown'});

    // Custom methods are available now :).
    // GET /accounts/123/users/messages?param=myParam
    users.customGET("messages", {param: "myParam"})

    var firstUser = users[0];

    // GET /accounts/123/users/456. Just in case we want to update one user :)
    $scope.userFromServer = firstUser.get();

    // ALL http methods are available :)
    // HEAD /accounts/123/users/456
    firstUser.head()

  });

}, function errorCallback() {
  alert("Oops error from server :(");
})

// Second way of creating Restangular object. URL and ID :)
var account = Restangular.one("accounts", 123);

// GET /accounts/123?single=true
$scope.account = account.get({single: true});

// POST /accounts/123/messages?param=myParam with the body of name: "My Message"
account.customPOST({name: "My Message"}, "messages", {param: "myParam"}, {})

Back to top

Configuring Restangular

Properties

Restangular comes with defaults for all of its properties but you can configure them. So, if you don't need to configure something, there's no need to add the configuration. You can set all these configurations in RestangularProvider or Restangular service to change the global configuration or you can use the withConfig method in Restangular service to create a new Restangular service with some scoped configuration. Check the section on this later.

setBaseUrl

The base URL for all calls to your API. For example if your URL for fetching accounts is http://example.com/api/v1/accounts, then your baseUrl is /api/v1. The default baseUrl is an empty string which resolves to the same url that AngularJS is running, but you can also set an absolute url like http://api.example.com/api/v1 if you need to set another domain.

setExtraFields

These are the fields that you want to save from your parent resources if you need to display them. By default this is an Empty Array which will suit most cases

setParentless

Use this property to control whether Restangularized elements to have a parent or not. So, for example if you get an account and then get a nested list of buildings, you may want the buildings URL to be simple /buildings/123 instead of /accounts/123/buildings/123. This property lets you do that.

This method accepts 1 parameter, it could be:

setDefaultHttpFields

$http from AngularJS can receive a bunch of parameters like cache, transformRequest and so on. You can set all of those properties in the object sent on this setter so that they will be used in EVERY API call made by Restangular. This is very useful for caching for example. All properties that can be set can be checked here: http://docs.angularjs.org/api/ng.$http#parameters

addElementTransformer

This is a hook. After each element has been "restangularized" (Added the new methods from Restangular), the corresponding transformer will be called if it fits.

This should be used to add your own methods / functions to entities of certain types.

You can add as many element transformers as you want. The signature of this method can be one of the following:

setTransformOnlyServerElements

This sets whether transformers will be run for local objects and not by objects returned by the server. This is by default true but can be changed to false if needed (Most people won't need this).

setOnElemRestangularized

This is a hook. After each element has been "restangularized" (Added the new methods from Restangular), this will be called. It means that if you receive a list of objects in one call, this method will be called first for the collection and then for each element of the collection.

I favor the usage of addElementTransformer instead of onElemRestangularized whenever possible as the implementation is much cleaner.

This callback is a function that has 4 parameters:

This can be used together with addRestangularMethod (Explained later) to add custom methods to an element

setResponseInterceptor

This is deprecated. Use addResponseInterceptor since you can add more than one.

addResponseInterceptor

The responseInterceptor is called after we get each response from the server. It's a function that receives this arguments:

Some of the use cases of the responseInterceptor are handling wrapped responses and enhancing response elements with more methods among others.

The responseInterceptor must return the restangularized data element.

setRequestInterceptor

This is deprecated. Use addRequestInterceptor since you can add more than one.

addRequestInterceptor

The requestInterceptor is called before sending any data to the server. It's a function that must return the element to be requested. This function receives the following arguments:

setFullRequestInterceptor

This is deprecated. Use addFullRequestInterceptor since you can add more than one.

addFullRequestInterceptor

This adds a new fullRequestInterceptor. The fullRequestInterceptor is similar to the requestInterceptor but more powerful. It lets you change the element, the request parameters and the headers as well.

It's a function that receives the same as the requestInterceptor plus the headers and the query parameters (in that order).

It can return an object with any (or all) of following properties:

If a property isn't returned, the one sent is used.

setErrorInterceptor

The errorInterceptor is called whenever there's an error. It's a function that receives the response, the deferred (for the promise) and the Restangular-response handler as parameters.

The errorInterceptor function, whenever it returns false, prevents the promise linked to a Restangular request to be executed. All other return values (besides false) are ignored and the promise follows the usual path, eventually reaching the success or error hooks.

The feature to prevent the promise to complete is useful whenever you need to intercept each Restangular error response for every request in your AngularJS application in a single place, increasing debugging capabilities and hooking security features in a single place.


var refreshAccesstoken = function() {
    var deferred = $q.defer();

    // Refresh access-token logic

    return deferred.promise;
};

Restangular.setErrorInterceptor(function(response, deferred, responseHandler) {
    if(response.status === 403) {
        refreshAccesstoken().then(function() {
            // Repeat the request and then call the handlers the usual way.
            $http(response.config).then(responseHandler, deferred.reject);
            // Be aware that no request interceptors are called this way.
        });

        return false; // error handled
    }

    return true; // error not handled
});

setRestangularFields

Restangular requires 7 fields for every "Restangularized" element. These are:

Also all of Restangular methods and functions are configurable through restangularFields property. All of these fields except for id and selfLink are handled by Restangular, so most of the time you won't change them. You can configure the name of the property that will be binded to all of this fields by setting restangularFields property.

setMethodOverriders

You can now Override HTTP Methods. You can set here the array of methods to override. All those methods will be sent as POST and Restangular will add an X-HTTP-Method-Override header with the real HTTP method we wanted to do.

setJsonp

Typical web browsers prohibit requesting data from a server in a different domain (same-origin policy). JSONP or "JSON with padding" is a communication technique used in JavaScript programs running in web browsers to get around this.

For JSONP to work, a server must know how to reply with JSONP-formatted results. JSONP does not work with JSON-formatted results. The JSONP parameters passed as arguments to a script are defined by the server.

By setting the value of setJsonp to true, both get and getList will be performed using JSonp instead of the regular GET.

You will need to add the 'JSON_CALLBACK' string to your URLs (see $http.jsonp). You can use setDefaultRequestParams to accomplish this:

RestangularProvider.setDefaultRequestParams('jsonp', {callback: 'JSON_CALLBACK'});

setDefaultRequestParams

You can set default Query parameters to be sent with every request and every method.

Additionally, if you want to configure request params per method, you can use requestParams configuration similar to $http. For example RestangularProvider.requestParams.get = {single: true}.

Supported method to configure are: remove, get, post, put, common (all)

// set params for multiple methods at once
Restangular.setDefaultRequestParams(['remove', 'post'], {confirm: true});

// set only for get method
Restangular.setDefaultRequestParams('get', {limit: 10});

// or for all supported request methods
Restangular.setDefaultRequestParams({apikey: "secret key"});

setFullResponse

You can set fullResponse to true to get the whole response every time you do any request. The full response has the restangularized data in the data field, and also has the headers and config sent. By default, it's set to false. Please note that in order for Restangular to access custom HTTP headers, your server must respond having the Access-Control-Expose-Headers: set.

// set params for multiple methods at once
Restangular.setFullResponse(true);

Or set it per service

// Restangular service that uses setFullResponse
app.factory('RestFulResponse', function(Restangular) {
  return Restangular.withConfig(function(RestangularConfigurer) {
    RestangularConfigurer.setFullResponse(true);
  });
});

// Let's use it in the controller
app.controller('MainCtrl', function(Restangular, RestFulResponse) {

  // Uses full response configuration
  RestFulResponse.all('users').getList().then(function(response) {
    $scope.users = response.data;
    console.log(response.headers);
  });
});

setDefaultHeaders

You can set default Headers to be sent with every request. Send format: {header_name: header_value}

// set default header "token"
RestangularProvider.setDefaultHeaders({token: "x-restangular"});

setRequestSuffix

If all of your requests require to send some suffix to work, you can set it here. For example, if you need to send the format like /users/123.json you can add that .json to the suffix using the setRequestSuffix method

setUseCannonicalId

You can set this to either true or false. By default it's false. If set to true, then the cannonical ID from the element will be used for URL creation (in DELETE, PUT, POST, etc.). What this means is that if you change the ID of the element and then you do a put, if you set this to true, it'll use the "old" ID which was received from the server. If set to false, it'll use the new ID assigned to the element.

setPlainByDefault

You can set this to true or false. By default it's false. If set to true, data retrieved will be returned with no embed methods from restangular.

setEncodeIds

You can set here if you want to URL Encode IDs or not. By default, it's true.

Back to top

Accessing configuration

You can also access the configuration via RestangularProvider and Restangular via the configuration property if you don't want to use the setters. Check it out:

Restangular.configuration.requestSuffix = '/';

Back to top

How to configure them globally

You can configure this in either the config or the run method. If your configurations don't need any other services, then I'd recommend you do them in the config. If your configurations depend on other services, you can configure them in the run using Restangular instead of RestangularProvider

Configuring in the config

app.config(function(RestangularProvider) {
    RestangularProvider.setBaseUrl('/api/v1');
    RestangularProvider.setExtraFields(['name']);
    RestangularProvider.setResponseExtractor(function(response, operation) {
        return response.data;
    });

    RestangularProvider.addElementTransformer('accounts', false, function(element) {
       element.accountName = 'Changed';
       return element;
    });

    RestangularProvider.setDefaultHttpFields({cache: true});
    RestangularProvider.setMethodOverriders(["put", "patch"]);

    // In this case we are mapping the id of each element to the _id field.
    // We also change the Restangular route.
    // The default value for parentResource remains the same.
    RestangularProvider.setRestangularFields({
      id: "_id",
      route: "restangularRoute",
      selfLink: "self.href"
    });

    RestangularProvider.setRequestSuffix('.json');

    // Use Request interceptor
    RestangularProvider.setRequestInterceptor(function(element, operation, route, url) {
      delete element.name;
      return element;
    });

    // ..or use the full request interceptor, setRequestInterceptor's more powerful brother!
    RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
      delete element.name;
      return {
        element: element,
        params: _.extend(params, {single: true}),
        headers: headers,
        httpConfig: httpConfig
      };
    });

});

Configuring in the run

// Here I inject the service BaseUrlCalculator which I need
app.run(function(Restangular, BaseUrlCalculator) {
    Restangular.setBaseUrl(BaseUrlCalculator.calculate());
});

Back to top

How to create a Restangular service with a different configuration from the global one

Let's assume that for most requests you need some configuration (The global one), and for just a bunch of methods you need another configuration. In that case, you'll need to create another Restangular service with this particular configuration. This scoped configuration will inherit all defaults from the global one. Let's see how.

// Global configuration
app.config(function(RestangularProvider) {
  RestangularProvider.setBaseUrl('http://www.google.com');
  RestangularProvider.setRequestSuffix('.json');
});

// Restangular service that uses Bing
app.factory('BingRestangular', function(Restangular) {
  return Restangular.withConfig(function(RestangularConfigurer) {
    RestangularConfigurer.setBaseUrl('http://www.bing.com');
  });
});

// Let's use them from a controller
app.controller('MainCtrl', function(Restangular, BingRestangular) {

  // GET to http://www.google.com/users.json
  // Uses global configuration
  Restangular.all('users').getList()

  // GET to http://www.bing.com/users.json
  // Uses Bing configuration which is based on Global one, therefore .json is added.
  BingRestangular.all('users').getList()
});

Back to top

Decoupled Restangular Service

There're some times where you want to use Restangular but you don't want to expose Restangular object anywhere. For those cases, you can actually use the service feature of Restangular.

Let's see how it works:

// Declare factory
module.factory('Users', function(Restangular) {
  return Restangular.service('users');
});

// In your controller you inject Users
Users.get(2) // GET to /users/2
Users.one(2).get() // GET to /users/2
Users.post({data}) // POST to /users

// GET to /users
Users.getList().then(function(users) {
  var user = users[0]; // user === {id: 1, name: "Tonto"}
  user.name = "Gonto";
  // PUT to /users/1
  user.put();
})

You can also use withHttpConfig on objects created by Restangular.service.

var personService = Restangular.service('person');
var entity = personService.withHttpConfig({transformRequest: function(data) {
  data.fullname = data.firstname + ' ' + data.lastname;
  return JSON.stringify(data);
}}).post({
  "lastname": "Mueller",
  "firstname": "Gerd"
}).then(function(resp) {
    console.log(resp);
});

We can also use Nested RESTful resources with this:

var Cars = Restangular.service('cars', Restangular.one('users', 1));

Cars.getList() // GET to /users/1/cars

Back to top

Methods description

There are 3 sets of methods. Collections have some methods and elements have others. There are are also some common methods for all of them

Restangular methods

These are the methods that can be called on the Restangular object.

Back to top

Element methods

Back to top

Collection methods

Back to top

Custom methods

Let's see an example of this:

// GET /accounts/123/messages
Restangular.one("accounts", 123).customGET("messages")

// GET /accounts/messages?param=param2
Restangular.all("accounts").customGET("messages", {param: "param2"})

All custom methods have an alias where you replace custom by do. For example, customGET is equal to doGET. Just pick whatever syntax you prefer.

Back to top

Copying elements

Before modifying an object, we sometimes want to copy it and then modify the copied object. We can't use angular.copy for this because it'll not change the this bound in the functions we add to the object. In this cases, you must use Restangular.copy(fromElement).

Back to top

Enhanced promises

Restangular uses enhanced promises when returning. What does this mean? All promises returned now have 2 additional methods and collection promises have 3. These are the methods:

I know these explanations are quite complicated, so let's see an example :D.

var buildings = Restangular.all("buildings").getList();

// New promise after adding the new building
// Now you can show in scope this newBuildings promise and it'll show all the buildings
// received from server plus the new one added
var newBuildings = buildings.push({name: "gonto"});

var newBuildingsSame = buildings.call("push", {name: "gonto"});

// This is a promise of a number value. You can show it in the UI
var lengthPromise = buildings.get("length");

lengthPromise.then(function(length) {
  // Here the length is the real length value of the returned collection of buildings
});

Back to top

Using values directly in templates

Since Angular 1.2, Promise unwrapping in templates has been disabled by default and will be deprecated soon.

This means that the following will cease to work:

$scope.accounts = Restangular.all('accounts').getList();
<tr ng-repeat="account in accounts">
  <td>{{account.name}}</td>
</tr>

As this was a really handy way of working with Restangular, I've made a feature similar to $resource that will enable this behavior again:

$scope.accounts = Restangular.all('accounts').getList().$object;
<tr ng-repeat="account in accounts">
  <td>{{account.name}}</td>
</tr>

The $object property is a new property I've added to promises. By default, it'll be an empty array or object. Once the sever has responded with the real value, that object or array is filled with the correct response, therefore making the ng-repeat work :). Pretty neat :D

Back to top

Using Self reference resources

A lot of REST APIs return the URL to self of the element that you're querying. You can use that with Restangular so that you don't have to create the URLs yourself, but use the ones provided by the server.

Let's say that when doing a GET to /people you get the following

[{
  name: "Martin",
  lastName: "Gontovnikas"
  self: {
    link: 'http://www.example.com/people/gonto'
  }
}, {
  name: "John",
  lastName: "Wayne"
  self: {
    link: 'http://www.example.com/people/jhonny'
  }
}]

In this case, as you can see, the URL to each element can't be guessed so we need to use that to reference the element. Restangular supports both relative and absolute URLs :).

How do we do this with Restangular?

First, we need to configure the path for the link to self. For that, in the config we do:

RestangularProvider.setRestangularFields({
  selfLink: 'self.link'
});

Then, we can just use this :)

// Instead of using all we could also use allUrl to set a URL
// Restangular.allUrl('people', 'http://www.example.com/people')

Restangular.all('people').getList().then(function(people) {

  var gonto = people[0];

  gonto.name = "Owned";

  // This will do a PUT to http://www.example.com/people/gonto
  // It uses the self linking property :D
  gonto.put()
})

Back to top

URL Building

Sometimes, we have a lot of nested entities (and their IDs), but we just want the last child. In those cases, doing a request for everything to get the last child is overkill. For those cases, I've added the possibility to create URLs using the same API as creating a new Restangular object. This connections are created without making any requests. Let's see how to do this:


var restangularSpaces = Restangular.one("accounts",123).one("buildings", 456).all("spaces");

// This will do ONE get to /accounts/123/buildings/456/spaces
restangularSpaces.getList()

// This will do ONE get to /accounts/123/buildings/456/spaces/789
Restangular.one("accounts", 123).one("buildings", 456).one("spaces", 789).get()

// POST /accounts/123/buildings/456/spaces
Restangular.one("accounts", 123).one("buildings", 456).all("spaces").post({name: "New Space"});

// DELETE /accounts/123/buildings/456
Restangular.one("accounts", 123).one("buildings", 456).remove();

Back to top

Using local $http configuration

There're sometimes when you want to set a specific configuration $http configuration just for one Restangular's call. For that, you can use withHttpConfig. You must call that method just before doing the HTTP request. Let's learn how to use it with the following example:

Restangular.one('accounts', 123).withHttpConfig({timeout: 100}).getList('buildings');

$scope.account.withHttpConfig({timeout: 100}).put();

Back to top

Creating new Restangular Methods

Let's assume that your API needs some custom methods to work. If that's the case, always calling customGET or customPOST for that method with all parameters is a pain in the ass. That's why every element has a addRestangularMethod method.

This can be used together with the hook addElementTransformer to do some neat stuff. Let's see an example to learn this:

// In your app configuration (config method)

// It will transform all building elements, NOT collections
RestangularProvider.addElementTransformer('buildings', false, function(building) {
        // This will add a method called evaluate that will do a get to path evaluate with NO default
        // query params and with some default header
        // signature is (name, operation, path, params, headers, elementToPost)

        building.addRestangularMethod('evaluate', 'get', 'evaluate', undefined, {'myHeader': 'value'});

        return building;
});

RestangularProvider.addElementTransformer('users', true, function(user) {
        // This will add a method called login that will do a POST to the path login
        // signature is (name, operation, path, params, headers, elementToPost)

        user.addRestangularMethod('login', 'post', 'login');

        return user;
});

// Then, later in your code you can do the following:

// GET to /buildings/123/evaluate?myParam=param with headers myHeader: value

// Signature for this "custom created" methods is (params, headers, elem) if it's a safe operation (GET, OPTIONS, etc.)
// If it's an unsafe operation (POST, PUT, etc.), signature is (elem, params, headers).

// If something is set to any of this variables, the default set in the method creation will be overridden
// If nothing is set, then the defaults are sent
Restangular.one('buildings', 123).evaluate({myParam: 'param'});

// GET to /buildings/123/evaluate?myParam=param with headers myHeader: specialHeaderCase

Restangular.one('buildings', 123).evaluate({myParam: 'param'}, {'myHeader': 'specialHeaderCase'});

// Here the body of the POST is going to be {key: value} as POST is an unsafe operation
Restangular.all('users').login({key: value});

Back to top

Adding Custom Methods to Collections

Create custom methods for your collection using Restangular.extendCollection(). This is an alias for:

  Restangular.addElementTransformer(route, true, fn);

Example:

  // create methods for your collection
  Restangular.extendCollection('accounts', function(collection) {
    collection.totalAmount = function() {
      // implementation here
    };

    return collection;
  });

  var accountsPromise = Restangular.all('accounts').getList();

  accountsPromise.then(function(accounts) {
    accounts.totalAmount(); // invoke your custom collection method
  });

Back to top

Adding Custom Methods to Models

Create custom methods for your models using Restangular.extendModel(). This is an alias for:

  Restangular.addElementTransformer(route, false, fn);

Back to top

Example:

  Restangular.extendModel('accounts', function(model) {
    model.prettifyAmount = function() {};
    return model;
  });

  var accountPromise = Restangular.one('accounts', 1).get();

  accountPromise.then(function(account) {
    account.prettifyAmount(); // invoke your custom model method
  });

Back to top

FAQ

How can I handle errors?

Errors can be checked on the second argument of the then.

Restangular.all("accounts").getList().then(function() {
  console.log("All ok");
}, function(response) {
  console.log("Error with status code", response.status);
});

I need to send one header in EVERY Restangular request, how do I do this?

You can use defaultHeaders property for this or $httpProvider.defaults.headers, whichever suits you better. defaultsHeaders can be scoped with withConfig so it's really cool.

Can I cache requests?

$http can cache requests if you send the property cache to true. You can do that for every Restangular request by using defaultHttpFields property. This is the way:

RestangularProvider.setDefaultHttpFields({cache: true});

Can it be used in $routeProvider.resolve?

Yes, of course. Every method in Restangular returns a promise so this can be used without any problem.

How can I send a delete WITHOUT a body?

You must add a requestInterceptor for this.

RestangularProvider.setRequestInterceptor(function(elem, operation) {
  if (operation === "remove") {
     return null;
  }
  return elem;
})

My response is actually wrapped with some metadata. How do I get the data in that case?

So, let's assume that your data is the following:

 // When getting the list, this is the response.
{
  "status":"success",
  "data": {
    "data": [{
      "id":1,
      // More data
    }],
    "meta": {
      "totalRecord":100
    }
  }
}

// When getting a single element, this is the response.
{
  "status":"success",
  "data": {
    "id" : 1
    // More data
  }
}

In this case, you'd need to use RestangularProvider's addResponseInterceptor. See the following:

app.config(function(RestangularProvider) {

    // add a response interceptor
    RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
      var extractedData;
      // .. to look for getList operations
      if (operation === "getList") {
        // .. and handle the data and meta data
        extractedData = data.data.data;
        extractedData.meta = data.data.meta;
      } else {
        extractedData = data.data;
      }
      return extractedData;
    });

});

I use Mongo and the ID of the elements is _id not id as the default. Therefore requests are sent to undefined routes

What you need to do is to configure the RestangularFields and set the id field to _id. Let's see how:

RestangularProvider.setRestangularFields({
  id: "_id"
});

What if each of my models has a different ID name like CustomerID for Customer

In some cases, people have different ID name for each entity. For example, they have CustomerID for customer and EquipmentID for Equipment. If that's the case, you can override Restangular's getIdFromElem. For that, you need to do:

RestangularProvider.configuration.getIdFromElem = function(elem) {
  // if route is customers ==> returns customerID
  return elem[_.initial(elem.route).join('') + "ID"];
}

With that, you'd get what you need :)

How can I send files in my request using Restangular?

This can be done using the customPOST / customPUT method. Look at the following example:

Restangular.all('users')
          .withHttpConfig({transformRequest: angular.identity})
          .customPOST(formData, undefined, undefined,
            { 'Content-Type': undefined });

This basically tells the request to use the Content-Type: multipart/form-data as the header. Also formData is the body of the request, be sure to add all the params here, including the File you want to send of course. There is an issue already closed but with a lot of information from other users and @mgonto as well: GitHub - Restangular

How do I handle CRUD operations in a List returned by Restangular?

The best option for doing CRUD operations with a list, is to actually use the "real" list, and not the promise. It makes it easy to interact with it.

Let's see an example :).

// Here we use then to resolve the promise.
Restangular.all('users').getList().then(function(users) {
  $scope.users = users;
  var userWithId = _.find(users, function(user) {
    return user.id === 123;
  });

  userWithId.name = "Gonto";
  userWithId.put();

  // Alternatively delete the element from the list when finished
  userWithId.remove().then(function() {
    // Updating the list and removing the user after the response is OK.
    $scope.users = _.without($scope.users, userWithId);
  });

});

When you actually get a list by doing

$scope.owners = house.getList('owners').$object;

You're actually assigning a Promise to the owners value of the $scope. As Angular knows how to process promises, if in your view you do an ng-repeat of this $scope variable, results will be shown once the promise is resolved (Response arrived). However, changes to that promise that you do from your HTML won't be seen in the scope, as it's not a real array. It's just a promise of an array.

Removing an element from a collection, keeping the collection restangularized

While the example above removes the deleted user from the collection, it also overwrites the collection object with a plain array (because of _.without) which no longer knows about its Restangular attributes.

If want to keep the restangularized collection, remove the element by modifying the collection in place:

userWithId.remove().then(function() {
  var index = $scope.users.indexOf(userWithId);
  if (index > -1) $scope.users.splice(index, 1);
});

When I set baseUrl with a port, it's stripped out.

It won't be stripped out anymore as I've ditched $resource :). Now you can happily put the port :).

How can I access the unrestangularized element as well as the restangularized one?

In order to get this done, you need to use the responseExtractor. You need to set a property there that will point to the original response received. Also, you need to actually copy this response as that response is the one that's going to be restangularized later

RestangularProvider.setResponseExtractor(function(response) {
  var newResponse = response;
  if (angular.isArray(response)) {
    angular.forEach(newResponse, function(value, key) {
      newResponse[key].originalElement = angular.copy(value);
    });
  } else {
    newResponse.originalElement = angular.copy(response);
  }

  return newResponse;
});

Alternatively, if you just want the stripped out response on any given call, you can use the .plain() method, doing something like this:


$scope.showData = function () {
  baseUrl.post(someData).then(function(response) {
    console.log(response.plain());
  });
};

Addendum : If you want originalElement to be the original response object instead of having an original value for each key in your newResponse array, replace

      newResponse[key].originalElement = angular.copy(value);

By

      newResponse.originalElement[key] = angular.copy(value);

Restangular fails with status code 0

This is typically caused by Cross Origin Request policy. In order to enable cross domain communication and get correct response with appropriate status codes, you must have the CORS headers attached, even in error responses. If the server does not attach the CORS headers to the response then the XHR object won't parse it, thus the XHR object won't have any response body, status or any other response data inside which typically will cause your request to fail with status code 0.

Why does this depend on Lodash / Underscore?

This is a very good question. I could've done the code so that I don't depend on Underscore nor Lodash, but I think both libraries make your life SO much easier. They have all of the "functional" stuff like map, reduce, filter, find, etc. With these libraries, you always work with immutable stuff, you get compatibility for browsers which don't implement ECMA5 nor some of these cool methods, and they're actually quicker. So, why not use it? If you've never heard of them, by using Restangular, you could start using them. Trust me, you're never going to give them up after this!

Back to top

How do I cancel a request?

Sometimes you may wish to cancel a request, this is how you would do it:

var canceler = $q.defer();
Restangular.all('users').withHttpConfig({timeout: canceler.promise}).get();
canceler.resolve(); // cancels the request

This is a little counterintuitive, so let me explain. Restangular is built on top of $http, which takes a timeout parameter. As per the $http docs:

timeout in milliseconds, or promise that should abort the request when resolved.

Resolving the promise (canceler in this case), will cancel the request.

Back to top

Supported Angular versions

Restangular supports all Angular versions from 1.0.X - 1.5.X

Also, when using Restangular with version >= 1.1.4, in case you're using Restangular inside a callback not handled by Angular, you have to wrap the whole request with $scope.apply to make it work or you need to run one extra $digest manually. Check out https://github.com/mgonto/restangular/issues/71

Back to top

Server Frameworks

Users reported that this server frameworks play real nice with Restangular, as they let you create a Nested RESTful Resources API easily:

Back to top

Releases Notes

New releases notes are together with releases in GitHub at: https://github.com/mgonto/restangular/releases

To see old releases notes, you can click here

Back to top

Contributors

Back to top

License

The MIT License

Copyright (c) 2014 Martin Gontovnikas http://www.gon.to/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Bitdeli Badge

Back to top