How to send objects with arrays using angular resource? - angularjs

I have a product object with a property called category_ids that is an array of ids.
I've added an $update method to my resource factory so I can send a PUT request.
When I PUT, the server receives data that looks like:
id: 1,
description: 'Yada yada',
category_ids: [1,2,3],
product: { id: 1, description: 'Yada yada' } //<-- I need category_ids in here
How can I get the category_ids array into the product node there?
More detail:
I'm just using angular's resource to update:
'use strict'
angular.module('myApp').factory 'Product', ($resource) ->
resource = $resource '/api/v1/products/:id', { id: '#id' },
update:
method: 'PUT'
return resource
Interestingly, this problem only happens with calling instance methods on my object. Calling the class methods on the factory itself works:
currentProduct.$update() <-- This does not give me the format I want!
Product.update(id: currentProduct.id, product: currentProduct) <-- This does :-\

You can add properties to $resource object like
currentProduct.category_ids = [1,2,3];
Now this property becomes request parameter for PUT request.
Lets suppose you have $resource factory name "Product" then following code will work for you
var currentProduct = new Product()
// Bind properties to resource object
currentProduct.id = 1
currentProduct.description = 'Yada yada'
currentProduct.category_ids = [1,2,3]
// Initiate PUT request.
currentProduct.$update()

I believe Rails expecting put request to follow pattern
/product/:id
so when you want to update product you should call it
product.$update({id:product.id});
and that will make request to url
http://api/v1/products?id=1
with request payload like
{"id":"1","description":"Yada yada","category_ids":[1,2,3]}
please see demo here
http://plnkr.co/edit/Utjoj6LirxvzMGSwoffo?p=preview

We would need the code of that $update method, but I suspect it is relying on the this keyword, which is different depending on which object you are calling the function from.
Could you please try this and let us know if it works:
currentProduct.$update.bind(Product) ();
If it does, this means that indeed $update expect this to be Product (which is not the case and is currentProduct in your example instead).

Create a angular service
// Angular Service
angular.module('myApp').factory('Product', function($resource,ENV)
return $resource(ENV.apiEndpoint+'/api/v1/products/:id', {}, {
update: { method: 'PUT' , params:{id:'#id'}}
});
});
In your controller you have to set product object
angular.module('myApp').controller('ProductController',
function ($scope,Product) {
var requestParams = {
id: 1, //Service extracts this parameter to form the url
product: {
id: 1,
description: 'Yada yada'
category_ids: [1,2,3]
}
}
Product.update(requestParams,function(success){
console.log(success)
},function(error){
console.log(error)});
});

Related

$resource - proper configuration for ngResource

I have 3 objects in my application, Games, Questions, and Answers.
The classes are configured as such:
class Game{
id;
Question[] questions;
}
class Question{
id;
text;
Answer[] answers;
}
class Answer{
id;
text;
}
I am trying to correctly configure an ngResource to handle this class setup. Ideally, what I'd like to achieve is something like this:
app.factory('gameRepository', function($resource){
var gameResource = $resource('/api/Games/:id', { id: '#id' });
return {
get: function(id){ return gameResource.get({id: id}); }
};
});
app.controller('myController', function(gameRepository){
var game = gameRepository.get(17);
var questions = game.$getQuestions();
var answers = questions[0].$getAnswers();
});
I know that some of this can be achieved by doing this:
var gameResource = $resource('/api/Games/:id/:action', { id: '#id', action: '#action' },
{
getQuestions: { method: 'GET', isArray: true, params: { action: 'Questions'}
},
);
However, I get stuck after this point. Ideally, what I'd like to do is have the $getAnswers method return an object from a different resource (a new questionsResource) and then in turn, have a $getAnswers method that returns from an answers resource. The goal here is to keep the methods on the actual resource object instead of extracting them to a separate factory/service call.
Additionally, I'd like to be able to request a specific question from the repository. Something like this:
var game = gameRepository.get(17);
var question = game.$getQuestion(1);
As far as I can tell, there's no way to pass a specific parameter to a $resource custom action the way I'm using them.
Can anybody point me in the right direction? Thanks!
This ended up being way easier than I thought.
The $resource function creates a function that all objects returned from the resource inherit. As a result you can do something like this:
gameResource.prototype.$getQuestion = function(id){ ... }

Loading Backbone model by custom attribute

Lets say, I have the following Backbone model :
var Meal = Backbone.Model.extend({
defaults: {
"appetizer": "caesar salad",
"entree": "ravioli",
"dessert": "cheesecake"
},
urlRoot : api/meals,
idAttribute : id,
// some other stuff
});
Assuming that I have a backend Spring MVC conroller that intercept GET requests, so when I fetch my model, using
myMeal.fetch();
my model gets loaded from the server.
for now everything is clear, my question is, what if I have another method on the backend that takes a string as parameter and return as responsebody, the right json element.
how can I make that call from my model ?
I'm looking for something like this :
var meal = new Meal({'entree': 'value'});
meal.fetch({
// if there is no id, and 'entree' is given, I want to call /
// api/findByEntree passing this.entree as parameter.
});
I want to make an Ajax call from the object itself to the backend, by specifying the url inside the Backbone model.
urlRoot can be a function so no need to override fetch. I believe you could just do something like this:
var Meal = Backbone.Model.extend({
defaults: {
"appetizer": "caesar salad",
"entree": "ravioli",
"dessert": "cheesecake"
},
urlRoot : function() {
return 'api/' + this.get('id') ? 'meals' : 'findByEntree';
},
idAttribute : id,
// some other stuff
});
You can override the default fetch, intercept the call, do some verification and then pass onto the original fetch:
var Meal = Backbone.Model.extend({
fetch: function(options) {
if(this.has('id')) {
Backbone.Model.prototype.fetch.call(this, options);
} else {
this.findByEntree(options);
}
},
fetchByEntree: function(options) {
...
}
});
however, keep in mind that you'll need some extra logic to deal with the case of trying to fetch a new Meal, which won't have neither id nor entree.

ember AfterModel of one to many creates empty array

I have 2 models. Session and Test.
App.Session = DS.Model.extend({
tests: DS.hasMany('test', {async: true}/*, {inverse: 'sessionID'}*/),
});
App.Test = DS.Model.extend({
session: DS.belongsTo('session', {async: true}, {inverse:'tests'}),
});
And I have a route to fetch the tests array (it isn't included in the JSON coming from the server)
App.SessionRoute = Ember.Route.extend({
model: function(params) {
return this.get('store').find('session', params.session_id);
},
afterModel: function(model) {
var promise;
promise = this.get('store').find('test', {
sessionId: model.get('id')
});
return this.controllerFor('tests').set('model', promise);
}
});
The thing is that the tests array in Session is still empty after the fetch.
{{ tests.length }}
is 0
When I log to console what is returned in AfterModel - I do have the data - however it is nested in a triple nested object (don't know if it is how it should be or not)
Class {content: Class, ember1416498812066: "ember400", __nextSuper: undefined, __ember_meta: Object, constructor: function…}ember1416498812066: "ember400"__ember_meta: Object__nextSuper: undefinedcontent: Class__ember1416498812066: "ember525"ember_meta: Object__nextSuper: undefinedcontent: Class__ember1416498812066: "ember524"ember_meta: Object__nextSuper: undefinedcontent: Array[1] isLoaded: truemanager: Classmeta: Objectquery: Objectstore: Classtype: BackslashUi.Test__proto__: ClassisFulfilled: true__proto__: Class__proto__: Class
The test object exists nicely in the ember chrome plugin (in "data" section)
Does anyone know what is wrong/how I can even debug this?
Don't set the model for the tests controller to the promise; set it to the result of the promise when it fulfills, and return the promise so that Ember will know when to go ahead.
afterModel: function(model) {
var testsController = this.controllerFor('tests');
var promise = this.get('store').find('test', {
sessionId: model.get('id')
});
return promise.then(function(tests) {
testsController.set('model', tests);
});
}
The automatic handling of promises for models is something that happens specifically in the context of the route's model hook. model examines the return value, sees if it is a promise. If it is, it waits for it to resolve before proceeding to pass the result of the promise to setupController etc. You can't just randomly set models to promises and expect them to work. Nobody is watching them or waiting for them to resolve or then'ing off them.

AngularJS: $resource with nested resources

I am using AngularJS $resource model to REST API. I have got something like this:
angular.module('libraryapp')
.factory('Book', function($resource){
return $resource('books/:id');
});
I am using in these way:
Book.get({ id: 42 }, function(book) {
console.log(book);
});
But I also want an endpoint to a subresource, let's say:
GET /books/:id/comments
How should I define it in module? May I extend Book in some way, to use it like this
Book.get({ id: 42 }).Comment.query(function(comments) {
console.log(comments);
});
You can easily reach nested RESTful resources with AngularJS $resource definitions.
The clue is to understand how the params parameter of each action definition (in the list of actions) in the $resource definition works. As the documentation says, it's an
Optional set of pre-bound parameters for this action. […]
angular.module('libraryApp').factory('Book', [
'$resource', function($resource) {
return $resource('books/:id/:subResource', {}, {
comments: { // The `comments` action definition:
params: {subResource: 'comments'},
method: 'GET'
}
});
}
]);
Given the above definition, you should still be able to use Book as before. For example Book.get({ id: 42 }) translates to a GET books/42/ request.
However, given the new :subResource part of the $resource URL ('books/:id/:subResource'), you now can generate a
GET books/42/comments
request by calling either Book.get({ id: 42, subResource: 'comments' }) or the much more short and elegant interface Book.comments({ id: 42 }) defined as your comments action.
As far as I know, you can't nest resources, but it's pretty simple to do what you're looking for:
You can define optional parameters which you can override in each resource (like category here) or even override the url (look at the otherUrl resource)
angular.module('libraryApp').factory('Book', [
'$resource', function($resource) {
return $resource('books/:id/:category', {}, {
comments: {
method: 'GET',
action: 'category'
},
otherUrls: {
method: 'GET',
url: 'books/:id/admin/:option'
}
});
}
]);
You may want to use Restangular instead as it handles nested resources and a clean and easy way.
As djxak pointed out, adding actions to the resource means that the returned value is the containing resource type, not the sub-resource type.
I solved a similar problem by creating a new resource with the sub-resource URL and modifying the prototype of the containing resource to add a function:
angular.module('libraryapp')
.factory('Book', function($resource){
var bookUrl = 'books/:id',
Book = $resource(bookUrl),
BookComment = $resource(bookUrl + /comments");
Book.prototype.getComments = function () {
return BookComment.query({id: this.id});
};
return $resource('books/:id');
});
The usage then becomes:
Book.get({ id: 42 }).getComments(function(comments) {
console.log(comments);
});
The only downside I see with this approach is that if you have a separate "Comment" resource that is accessed via a different URL, you have to duplicate the $resource initialisation code for the alternative endpoint. This seems a minor inconvenience though.

Backbone Model not compatible with underscore and ASP.NET MVC Web API Controller?

This is a two stage problem when working with backbone.js and a web api controller.
I have a simple web api controller that returns a JSON string, in fiddler the result looks like this:
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading":"The heading"
}
I use the following code to fetch a user from my web api
var user = new Usermodel({ id: "1" });
user.fetch({
success: function (u) {
console.log(u.toJSON());
}
});
now my backbone user object looks like this
{
id: "1",
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading": "The heading"
}
}
When I try to bind this backbone model object to my view template that looks like this
<form>
<input type="text" value="<%=Heading%>" />
<input type="submit" value="Save" />
</form>
i get, Heading is undefined but when I use id it binds just fine? It seems like underscore does not like the backbone model object and just want a plain JSON object just like the one I get from my web api?
The second problem with this is that when I save my model with user.save({ Heading: "my new heading }); the payload to my web api is the backbone model which is completely wrong because my api expects a user object like this to be sent to the server:
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading":"The heading"
}
and not the backbone model with the real object wrapped inside. Is it possible to solve so that underscore can handle backbone models and tell backbone to only send the payload that my end point expects?
You may be able to solve the problem by following these steps:
In addition to using fiddler to inspect your response, look at the response on the network tab of Chrome Developer Tools. If the response does not look like this, then your web api is not returning a valid json response, the problem is most likely within your web api. You need to get/provide more information about your web api to solve the problem. Verify that the response looks like this:
After verifying that the response from the web api is correct, check out the following jsfiddle I modified:
http://jsfiddle.net/J83aU/23/
Fix your client side code referencing the example I have provided.
Properly instantiate the Backbone objects.
Call the view.render function at the correct step, after the response is received from the server.
Make sure that the main content div is actually rendered before creating a view which depends on it for the 'view.el' property.
Declare the 'view.el' property properly, with a string rather than jQuery object.
Use development Backbone and underscore to enable debugging, an important concept when learning to use open source frameworks such as Backbone.
Use jsfiddle's echo/json api to mock a valid ajax json response, exactly as described in step 1.
The following json example you submitted is not even valid json, if you update your question with valid json example, it would be easier to solve the problem. It is unlikely that Backbone created this non-json structure and more likely that you have submitted it here incorrectly.
{
id: "1",
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading": "The heading"
}
}
Finally, try to provide a screenshot of the http headers or something for the problem that is occurring when you call model.save().
Read over the Backbone documentation for model.save() and make sure you are doing everything just as the example provided.
You may be able to workaround Backbone's funky save function by forcing your attributes into POST parameters using ajax options:
$.fn.serializeObject = function(){
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
var saveView = Backbone.View.extend({
events:{
'click #saveSubmitButton':'submit'
},
submit:function (event) {
event.preventDefault();
var view = this,
attributes = $('#saveForm').serializeObject();
this.model.save(attributes, {
data:attributes,
processData:true,
success:function (model) {
//....
}
});
},
render:function () {
//.......
}
});
The attributes property of your model should be unaltered. Send those to your template call:
var MyModel = Backbone.Model.extend();
var newModel = new MyModel({
"$type": "MvcApplication.Models.Article, MvcApplication",
"Heading":"The heading"
});
var html = _.template(templateVar, newModel.attributes);
In your templateVar, which is your templated markup, you should be able to reference $type and Heading directly.
If you have a look at the jsFiddle through a debugger like Firebug you can see that the way you construct the model's URL is not working out, because the forward slash gets encoded. Can you try to modify your model declaration to this:
var Usermodel = Backbone.Model.extend({
url: function () {
return '/api/page/articles/' + this.get('id');
}
});
var user = new Usermodel({
id: '85'
});
And see if you still get the same JSON. Basically if you don't have a Backbone.sync override you are using built-in retrieval that for one shouldn't produce invalid JSON.

Resources