I am trying to use AngularJS to retrieve a JSON array (a list of strings). I have created a Resource, like so:
packageResource.factory('Package', ['$resource', function($resource) {
return $resource('/static/package.json', {}, {
'get': {
method: 'GET',
transformResponse: function (data) {
return angular.fromJson(data)
},
isArray: true
}
});
}]);
And my Controller like this:
MainController = [
'$scope', 'Package', function($scope, Saved, Rules) {
$scope.package = (Package.get());
}
];
And my template:
<ul>
<li ng-repeat="item in package">{{item}}</li>
</ul>
<p> {{package}}</p>
I expect it to display a list of all the items in the package.json array, and then, for testing, I added the {{package}} and would expect it to print the contents, but instead, I get:
{}
{}
{}
{}
{"0":"s","1":"r","2":"d"}
The JSON file contains the following:
[1,3,6,"srd"]
However, if I change my package.json to an object, it works perfectly. For example:
{
"author": "John",
"name": "project",
"version": 1
}
And of course, change isArray to false. I get:
John
project
1
So it appears that something can't handle an array, and mangles it into an object. I am having trouble figuring out what - all the Angular documentation shows Resource and Scope working with arrays, and it doesn't make since for them not to. I explicitly added the isArray: true flag to my Resource definition, but no luck.
I am using Angular version 1.0.8. I am a complete newbie, so it may be something painfully obvious, but I have been trying to get this to work for over a day now.
isArray=true means your json data looks like an array of objects, aka, a list. Each item in the array must be an object. so your json data needs to be [{name:value,name2:value2},{etc},{etc}].
isArray=false means your json data is a singular object.
This is the standard way of representing the restful data that $resource was designed to work with.
Related
I am a beginner in AngularJS. I used the $resource service to get a specific object of type person based on its id from the server in order to update this object.
Resource service:
moduleName.factory('Person', function ($resource) {
return $resource('/application/person/:id', {}, {
show: { method: 'GET' , params: {id: '#id'}},
});
});
The object that I received and it displayed in my browser using the {{}} is:
{
"id": "560cf96ee85532035928889e",
"firstName": "Christiane",
"gender": "Male",
"flight": {
"id": null,
"arrivalDate": "2015-01-05 11:30",
"arrivalAirport": {
"name": "Frankfurt"
},
"stopovers": []
}
}
In this form I have a dropdownlist that contains the Airport list, I expect that the value of the arrivalAirport will be selected in this dropdown list, but no value was selected, I try to set the selected value of this dropdown list in the AngularJs controller of this view.Before setting the value of this object I try to access to their value by this code inside the controller.
moduleName.controller('PersonDetailController',function ($scope, $routeParams, Person,$location) {
$scope.person = Person.show({id: $routeParams.id});
console.log($scope.person.flight.arrivalAirport);
});
when I want to get the value of the object flight.arrivalAirport nested in person object I got this error.
TypeError: Cannot read property 'arrivalAirport' of undefined
How can I display the right value selected in the dropdown list and how can I access to the other object like stopovers array in order to update their values?
Thank you.
When you call console.log Person's data hasn't been retrieved yet. You should do something like this:
$scope.person = Person.show({id: $routeParams.id}, function(person) {
console.log(person.flight.arrivalAirport);
});
In general, any code working on data received asynchronously can't be put right after the call because it's very unlikely that data has been retrieved at that point, you should use callbacks and promises.
Your view should update automatically, and you said it does, at least for the part showing the json. In order to make the dropdown update you should bind its values to a property inside $scope.person
If the dropdown doesn't update then there must be some other error and you might need to post the dropdown's code too.
If you want to manually update the dropdown's list instead then you need to put the relevant code inside the callback (the function i added to "person.show" in the code above) in order to make it work
I have a server side API method which returns an array of locations in JSON and have created a resource in Angular to retrieve the entire array of locations upon loading.
All locations also have infinite nested/children locations which is contained within an array inside of the 'children' property for each location. Here's an example to demonstrate what the server response may look like:
[
{
id: 1,
name: 'Top Level',
children:
[
{
id: 2,
name: 'Second Level',
children: [ ... ]
}
]
}
]
The $save() method works correctly when calling it on top level nodes, as expected (although the payload also includes the nested children property when executing the POST).
This however does not work when attempting to call $save() on any of the nested 'children' nodes. This leads me to the conclusion that the resource methods are not attached in a recursive manner; but is there a way in which one may specify a recursive or nested property in a resource (I've found nothing in the official angular documentation along these lines) so that I may attach the $save() method to nested children nodes as well?
Here's my existing code for reference:
angular.module('locations', [ 'ngResource' ])
.factory('Locations', function( $resource )
{
return $resource(
'./locations', {},
{ query: { method: 'GET', isArray: true } }
);
});
I'm looking to add factory/service calls for the URLs below to an AngularJS project in a way that follows the DRY principle. The project uses ngResource.
http://localhost/vehicles/{type:car|truck}/drive/{2wd|4wd}?sort={"start":"1", "limit':"10", "sortBy": "make"}
http://localhost/vehicles/{type:car|truck}/drive/{2wd|4wd}/count
http://localhost/vehicles/bestselling/{type:car|truck}?sort={"start":"1", "limit':"10", "sortBy": "make"}
http://localhost/vehicles/bestselling/{type:car|truck}/count
All calls are HTTP GET
The URL path parameters "{type:car|truck}" can be either one of "car" or "truck" the same goes for "{2wd|4wd}".
The URLs ending with count return the number of items (for pagination); the rest return the list of items to be displayed.
How can I define the factory/service calls for these resources in Angular? I have not had any luck finding an answer for this; the closest I've found is this
Disclaimer: I have no experience with AngularJS
My solution:
services.js
resources.factory('Constants', [
function() {
return {
RESOURCE_URL: "http://localhost/vehicles"
}
}
]);
resources.factory('Rest', ['Constants', '$resource', function(C, $resource) {
return {
BestSellingCount: $resource(C.RESOURCE_URL + '/bestselling/:type/count', {
type: '#type'},{})
BestSelling: $resource(C.RESOURCE_URL + '/bestselling/:type', {
type: '#type'}, {
getBestSelling: { method:'GET', params: {sort: {"start":"1", "limit':"10", "sortBy": "make"}}, isArray: true)}}})
}]);
And in my controller
$scope.bestsellingtrucks = Rest.BestSelling.getBestSelling({type:"truck"});
The default sort and pagination values get passed in as a query string
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.
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.