0

So I'm using this Rest API with ngResource to do get, query, post and update requests. What I'm looking for, is a way to define the structure for each entity.

For example, assuming we have:

module.factory('app.entity.item', function($resource) {
    return $resource('http://xmpl.io/items/:itemId', { itemId: '@id' });
});

I want to instantiate it in a controller like:

module.controller('AddItemCtrl', ['app.entity.item', function(Item) {
    $scope.item = new Item();
});

and bind it to the respective form in my template.

The actual problem that I have run into, is that I have to deal with 1:m tables. An example of the entity structure would be:

{
  "name": "",
  "categories": [],

  "list": [
    {
      "value": "",
      "list": [
        {
          "value": "",
          "list": [
            {
              "value": ""
            }
          ]
        }
      ]
    }
  ]
}

(A more thorough example in the fiddle below)

Now the first two fields are obviously not the problem. It is the third one. The list. Each one of these lists can have a variable number of items.

I am currently using ngRepeat and an add(type, context) method, which adds a new set of fields to the scope (value field in this example and child lists for the first two levels), which will appear in UI by ngRepeat so the user can fill it up and submit it to the service.

First off, I have to define the structure, so the UI would not be empty when the page loads.

module.controller('AddItemCtrl', ['app.entity.item', function(Item) {
    $scope.item = new Item({
      "name": "",
      "categories": [],

      "list": [
        {
          "value": "",
          "list": [
            {
              "value": "",
              "list": [
                {
                  "value": ""
                }
              ]
            }
          ]
        }
      ]
    });
});

But that is redundant. I have to do it everywhere! Another issue is that when the item.$save is called, the model is emptied (perhaps re-instantiated?) and the fields inside the list property (managed by the ngRepeat directive) are gone.

So I'm wondering, what would you do under such circumstances. Is there a way to define the entity (resource) structure?

SAMPLE: http://jsfiddle.net/g15sqd5s/3/

Fardin K.
  • 445
  • 9
  • 19

2 Answers2

1

trying to give simple answer - for simple structures I would use something like

module.factory('Item', function($resource) {
  var resource = $resource('http://xmpl.io/items/:itemId', { itemId: '@id' },
    // you can also define transformRequest here: 
    { transformRequest: function(data) {
      // data can be transformed here
      return angular.toJson(data);
    }});

  return angular.extend(resource.prototype, 
    {
       name: null,
       categories: []
    });
  });

but then be aware of need to 'flatten' the object.

and for the more complex model I would check restangular

similar topic is also discussed here: How can I extend the constructor of an AngularJS resource ($resource)?

Community
  • 1
  • 1
ciekawy
  • 2,238
  • 22
  • 35
  • I don't see what the transformRequest is being used for; and I'm not sure about manipulating the prototype, but it sure is a solution. +1 for that and the references. – Fardin K. Feb 18 '15 at 09:21
  • transformRequest is just to show that there is another opportunity to deal with complex/non standard cases. – ciekawy Feb 18 '15 at 10:41
  • aha! I'm aware of that, but I don't think it can be a solution in my case. – Fardin K. Feb 18 '15 at 13:52
0

I would go ahead and revise my model structure in the backend in the first place - the models on the client side should merely follow the ones already defined, rather than being re-defined in a transform block. So, to answer your question, the "default" model structure comes from the server. What you get in your $resource objects has the structure of what your server returns.

To start off, is it really ok to invoke $save on the Item model when the user has populated some values? What we want to save are obviously the lists associated with an item, not the item itself. A separate resource defined in the backend, say items/<item_id>/list, may be a cleaner solution. It may not scale very well, as you'll have to make a separate GET request for each item to fetch its list, but that's the proper RESTful way to do it.

Extending this approach to the example in your fiddle, I imagine a routing scheme like buildings/<building_id>/floors/<floor_id>/units/<unit_id> would be a proper solution. Making a GET request to buildings/ should yield you a list of buildings; each building in the array returned should be an instance of a Building model, which has the proper URL set so the user can perform a single POST and update only the building name, instead of sending back the whole structure back to the server. Applying this recursively to the nested resources should give you a clean and concise way to deal with model changes.

Regarding the UI part - I would go ahead and define three directives for buildings, floors and units, and let each one manage an array with the respective resources, also taking care for the UI bindings to the model values.

So how could a Building model look like?

var BuildingResource = $resource('/buildings/:id', { id: '@id' });

Invoking BuildingResource.query() should yield an array of existing buildings. Adding a new building could look like this:

var newBuilding = new BuildingResource();
newBuilding.$save().then(function(building) {
  $scope.buildings.push(building);
}, function(errData) {
  //Handle error here...
});

It should be easy to extend this pattern for the rest of the resources - note that what the server needs to return for every building is just the name and the id; knowing the id is sufficient to construct an URL (and a $resource object, respectively) to fetch the needed child resources (in this case, floors).

petkov.np
  • 511
  • 3
  • 4
  • The Models are not being REdefine. I am providing the user with a form to CREATE an entity that does not exist. I don't believe I should ask the server for an empty model! What you said about "the proper RESTful way to do it" is completely right in the case of my jsfiddle example; but we are working with a single entity; plus, we are trying to minimize requests. Welcome BTW. – Fardin K. Feb 23 '15 at 13:52
  • There's no single point in my example at which the server is asked for an empty model. The second code snippet creates a model on the client side which may contain whatever data is desired (e.g. name). The response of the `POST` request is then added to the scope array - it will contain a valid id as returned from the database. If you're working with a single entity, what does '1:m' mean in your original query? This implies that on top of the single 'base' entity, you'll have an array of entities. That's exactly what the proposed approach is meant to deal with. – petkov.np Feb 23 '15 at 14:33
  • My angular app does not care about the way the server handles it's models; It sees only one entity. (Assume that it simply makes sense in this case!) I mentioned 1:m for the readers to be able to visualize the structure. That was before I added the examples. --- The proposed approach does not solve the problem presented. I want to generate a form. I don't see how splitting the original entity to contain a hierarchy of other entities help solve that problem. – Fardin K. Feb 23 '15 at 14:44
  • The problem is not resource handling, it's form generation in general and, specifically, Entity Structure in the `resourceProvider` API. – Fardin K. Feb 23 '15 at 14:45
  • Then please go ahead and modify your fiddle. Right now we see add buttons there that add entities to arrays; no mention of a single entity and form generation. I think fiddles should serve the purpose to present a _relevant_ example illustrating the problem. – petkov.np Feb 23 '15 at 14:52
  • You may assume there is a single entity, or multiple entities; that's irrelevant to the question. My complaint is about your answer which is not related to the actual question! – Fardin K. Feb 23 '15 at 15:06