Stussa
Stussa

Reputation: 3415

AngularJS How To Transform Requests for Rails API

I'm adding AngularJS 1.x to a form for editing orders (being able to change the date, shipping, and address in this example). I have a Ruby on Rails JSON API with a few endpoints:

GET /addresses
[{ id: 1, line1: "...", line2: "...", zip: "..." }, ...]

GET /shippings
[{ id: 1, name: "priority" }, ...]

GET /orders/:id
{ date: "...", 
  shipping: { id: 1, name: "priority" }, 
  address: { id: 1, line1: "...", line2: "...", zip: "..." } }

PATCH /orders/:id
{ date: "...",
  shipping_id: 1,
  address_attributes: { line1: "...", line2: "...", zip: "..." } }

Inside my OrdersController I have the following:

var app = angular.module('app');

app.controller('OrdersController', ['$scope', 'Order', 'Address', 'Shipping',
  function($scope, Order, Address, Shipping) {
    $scope.order = Order.get({ id: 123 });
    $scope.addresses = Address.query();
    $scope.shippings = Shipping.query();

    $scope.save = function() {
      $scope.order.$save(function() {
        // ...
      }, function(error) { alert(error); });
    };
  }
]);

My form is pretty simple:

<form ng-submit="save()">
  <input type="date" ng-model="order.date" />

  <select ng-model="order.shipping" 
    ng-options="option.name for option in shippings track by option.id">
  </select>

  <input type="text" ng-model="order.address.line1" />
  <input type="text" ng-model="order.address.line2" />
  <input type="text" ng-model="order.address.zip" />

  <input type="submit" value="Save"/>
</form>

The save function hits my API endpoint - however the parameters are incorrect. I need to find a way to transform the between these formats:

{ date: "...", shipping: { ... }, address: { ... } }
{ date: "...", shipping_id: 1, address_attributes: { ... } } 

What is the recommended way of doing this in AngularJS? Is this something that should be done through interceptors? Should I be creating a new Order resource and copying over the attributes in the desired form order?

Upvotes: 4

Views: 153

Answers (2)

Kevin Sylvestre
Kevin Sylvestre

Reputation: 38032

AngularJS $resource supports setting transformRequest and transformResponse on each action (see https://docs.angularjs.org/api/ngResource/service/$resource). For your example:

angular.module("app").factory("OrderSerializer", function() {
  return function(data) {
    var parameters = angular.copy(data);

    if (parameters.address) {
      parameters.address_attributes = parameters.address;
      delete parameters.address;
    }

    if (parameters.shipping && parameters.s.id) {
      parameters.shipping_id = parameters.shipping.id;
      delete parameters.shipping;
    }

    return parameters;
  };
});

angular.module("app").factory("Order", ["$resource", "OrderSerializer",
  function($resource, serializer) {
    return $resource("...", { id: "@id" }, {
      "save": {
        method: "...",
        transformRequest: [serializer, angular.toJson],
      },
    });
  }
]);

If advanced transforms are required it may be worth looking into a replacement for ng-resource such as restangular.

Upvotes: 1

Shaun Scovil
Shaun Scovil

Reputation: 3987

Angular's $http (and $resource) service has a configuration option called transformRequest that accepts a function (or an array of functions) that can be used to transform request data.

Assuming you are using one of these services, this would be the most appropriate place to handle object mapping.

Example: JSFiddle

function MyController($http) {
  var vm = this;

  vm.data = {
    message: 'Hello World!'
  };

  vm.mapper = function(data, headersGetter) {
    data.greeting = data.message;
    delete data.message;
    return data;
  };

  $http({
    url: '/path/to/endpoint',
    method: 'PUT',
    data: vm.data,
    transformRequest: [vm.mapper, angular.toJson]
  });
}

Note that, in my example, I use an array of functions to transform my request. This is because, by default, $http uses angular.toJson to stringify the data object and I still want that behavior after my mapper function runs.

Upvotes: 1

Related Questions