I don't quite get how to properly use AngularJS's $resource. For example, I have a REST API that is returning data like this:
{
"status": "success",
"data": {
"expand": "schema,names",
"startAt": 0,
"maxResults": 10,
"total": 38,
"issues": [{ ... }, { ... }, {...}, ...]
}
}
Now what I am trying to figure out is how in AngularJS to use the $resource where each object in data.issues is returned as a resource (so in this case, get an array/collection of 10 resources) however it does not really seems like I can do that with AngularJS's $resource from the limited resource I have found on it, or can I?
$resource is expecting a classic "RESTful" API source. This means you'd have an endpoint where GET, PUT, POST, DELETE methods would all effect a given resource type, and the body of those messages would only include the resource, not the resource and a bunch of metadata.
For what you're trying to do, if that's the API you're stuck with, you're probably going to have to use $http to roll your own, as it looks like the JSON it's responding with contains a bunch of metadata that $resource won't care about.
The only other option would be to write some sort of httpInterceptor that would translate what you're getting back from your web service as into something $resource can handle a little more seamlessly.
While, semantically, your web service is probably generically "RESTful", it's not RESTful in the current classic standard of what that means.
More information on REST here
EDIT: Outside of the above information, without seeing the signature of your web API or knowing what you're trying to do, it will be hard to answer your question in any more detail.
With newer versions of angular, you can provide a callback to a resource action that will bypass an outer wrapper.
$resource('', {}, {
query: {
method: 'GET',
isArray: true,
transformResponse: function(response) {
return JSON.parse(response).data.issues;
}
}
});
Then in your resource callback function
Issue.query({
//query params
}, function(issues) {
//issues will be an array of your issues that you can process
//or just add to the scope to be iterated over
});
Looking at the code, blesh is correct so what I did was modify the base $resource code in order to support custom encoders/decoders to make is any to have $resource be able to work with any REST API that might have a customized format. Made a pull request to angularjs hopping they will include it so I don't have to maintian this separate fork : https://github.com/angular/angular.js/pull/1514
Related
I am trying to get information from a fantasy data API using AngularJS. I am using $resource to perform my get request in my controller, but I haven't been able to figure out how to correctly include the API key. Do I need to include it as a header? Thanks.
nflApp.controller('mainController', ['$scope','$resource','$routeParams', function($scope, $resource, $routeParams) {
$scope.fantasyAPI = $resource("https://api.fantasydata.net/nfl/v2/JSON/DailyFantasyPlayers/2015-DEC-28", { callback: "JSON_CALLBACK" }, { get: { method: "JSONP"}});
console.log($scope.fantasyAPI);
}]);
Below is the http request info from the site.
You should set a header with the API key, AngularJS will send them with every request in the following case:
$http.defaults.headers.common["Ocp-Apim-Subscription-Key"] = key;
When adding '.common' you are telling angular to send this in every request so you do not need to add it to every resource that hits the API.
A easy way to do that is by creating your own interceptors from $httpProvider at "config" fase.
To do that, just write something like:
mymodule.config(['$httpProvider', function($httpProvider){
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (config) {
config.headers['Ocp-Apim-Subscription-Key'] = SomeUserClass.AuthToken();
return config;
},
'response': function (response) {
return response;
}
};
});
});
You need to modify request header in JSONP. Unfortunately it is not possible. As the browser is responsible for header creation and you just can't manipulate that when using JSONP method.
how to change the headers for angularjs $http.jsonp
Set Headers with jQuery.ajax and JSONP?
From that link - https://johnnywey.wordpress.com/2012/05/20/jsonp-how-does-it-work/
Why NOT To Use JSONP?
Deciding against using JSONP is directly related to how it works. First of all, the only HTTP method you can use is GET since that is the only method script tags support. This immediately eliminates the use of JSONP as an option to interact with nice RESTful APIs that use other HTTP verbs to do fun stuff like CRUD. And while we’re on the subject of GET, note that using anything other than URL parameters to communicate with the server API (e.g. sending some JSON over) is also not possible. (You could encode JSON as a URL parameter, but shame on you for even thinking that.)
If they only work with header manipulation you will need to do that call from your server side.
I am having a serious issue with Angularjs and jQuery with this particular REST API. I am not in the same domain as said API, and can get the data back, however I am getting a "SyntaxError: invalid label "name" :{" error.
If I were to do something like $http.get or $.get I get a timeout after 10 seconds. However, if I use jsonp with either library I will see in Firebug that the data is returned in the net tab, however I get the error above in the console tab. After doing some research, I have seen plenty of people having issue with the API (a Jive product) and this specific line of text that is returned along with the JSON. The response looks something like this:
throw 'allowIllegalResourceCall is false.';
{"name":{ "givenName": "xxx"}}
The big problem is the first "throw" line. I have tried a bunch of ways to remove that line but I haven't found the proper way to do it. I apologize for not being able to provide a code sample, but if there is any way to get this work in Angularjs or jQuery, I will take it. I don't know if the answer lies in Angularjs interceptors, or transformResponse.
Any help that can be provided will be appreciated.
Thank you
AngularJs allows you do define the methods for transforming the http response data (so you can remove the first line of the response data). You can do this either for a single request or add a httpInterceptor.
Single request:
$http.get('...', {
transformResponse: $http.defaults.transformResponse.unshift(function(data) {
// Remove first line of response
data.split("\n").slice(1).join("\n")
}
});
HttpInterceptor
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
'request': function(config) {
config.transformResponse.unshift(function(data) {
return data.split("\n").slice(1).join("\n")
})
return config;
}
}
})
}])
Plunker: http://plnkr.co/edit/6WCxcpmRKxIivl4yK4Fc?p=preview
How can I use promise with $resource?
This is my service,
app.service("friendService",function( $resource, $q ) {
// Return public API.
return({
addFriend: addFriend,
updateFriend: updateFriend,
getFriends: getFriends,
removeFriend: removeFriend
});
function updateFriend( friend ) {
var postData = {
id: friend.id,
name: friend.name
};
var request = $resource("api/update.php", null,{
update: {
method:'PUT',
data:postData
}
});
return( request.$promise.then( handleSuccess, handleError ) );
}
I get this error,
TypeError: request.$promise is undefined
What is the correct way doing it with $resource?
Change from
return( request.$promise.then( handleSuccess, handleError ) );
to
return request.update().$promise.then(handleSuccess, handleError);
That said, using $resource like this is quite inefficient while not taking any advantage of it. It's better to replace with $http.
You should simplify your service to actually BE the $resource
app.factory('friendService', [ '$resource', function($resource) {
return $resource('/api/friends/:id', null, {
'update' : {
method : 'PUT'
}
});
} ]);
This automatically provides the following endpoints (which actually is the cool thing about $resource):
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
Here are some usage examples:
friendService.query(success, error); // GET /friends
friendService.get({ id : "exampleId" }, success, error); // GET /friends/exampleId
friendService.save({/* no params */}, friendObjectWithId, success, error); // POST /friends/idTakenFromObject
friendService.delete({ id : "exampleId" }, {}, success, error); // DELETE /friends/exampleId
friendService.update({/* no params */}, friendObjectWithId, success, error); // PUT /friends/idTakenFromObject
So, as this line of the documentation describes, you dont need the $promise to specify the callbacks:
non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
So you can simply do something like this:
friendService.update({}, friendObject, successHandler, errorHandler)
Short answer:
I think you are misunderstanding what $resource is, since you're trying to use it as you would use $http.
$resource is a "wrapper" around $http to provide a Object Oriented CRUD way to interact with a RESTful api. (DOCS explain it well and provide good examples)
From your URL, I don't think you're actually using a REST api so it would probably be better to use $http service instead of using $resource service.
Regardless, here's a working fiddle.
Resource and REST API
A resource, in the context of angular, corresponds to a resource in context of REST and so, it will expect your webservice to behave like a RESTful app. To explain it further, let' take your "Friend" as example... (I will be reworking your URLS to better match a REST API)
API Definition
Take the following REST+CRUD conformant scheme (for a Friend resource)
Resource URI Methods allowed
Friend Collection api/friend GET, POST
Friend api/friend/:id GET, PUT
The basic idea here is that each Resource is univocally represented by a URI (that's actually the definition of URI: -> Uniform Resource Identifier) and the HTTP Method (Verb) is used to define the action that will be performed on said Resource.
Of course, REST is much more than this and I suggest you read this SO POST or this funny article or even Roy Fielding's dissertation (the guy that came up with REST) that explain the concept a lot better than I ever can hope for.
URL Structure
This issue is prone to hot debate, and you can read some interesting points here in this SO Post and an article from Roy Fielding partially addressing this too. To sum up, REST does not require clean URLs. Actually, it doesn't require ANY kind of semantically logic URL structure.
What REST APIs must be is hypertext-driven, that is, given an entry point (URL), the API must be self explanatory so that a client can "discover" resources and relations by itself, with type of resources given by media-types. That means, if an url changes, the API doesn't break!!
So, in practical terms, this can be a valid:
Home /
Friend Collection /foo
Friend Resource 1 /bar
Friend Resource 2 /baz
As well as this can be valid :
Home index.php
Friend Collection index.php?q=api/friend
Friend Resource 1 index.php?q=api/friend/1
Friend Resource 2 index.php?q=api/friend/2
Or it's cousin, using mod_reqrite to make "clean URLs", can be valid
Home /
Friend Collection /api/friend
Friend Resource 1 /api/friend/1
Friend Resource 2 /api/friend/1
or even this can be valid...
Home /index.php
Friend Collection /friend.php
Friend Resource 1 /friend_1.php
Friend Resource 2 /friend_2.php
The server, in no way, is obliged to follow any pattern. However, that doesn't mean you shouldn't adhere to a structure, must mostly for SEO purposes (or human readability). And, in the last example, it might be hard to develop a sane webservice that relies on individual scripts for each individual resource. (you might not violate REST principles, but you will probably violate some basic programming rules, such as DRY...)
Also, angular-resource is (kind of) opinionated about url structure. It's not an absolute requirement but...
Regarding your specific question, yes, you would need mod_rewrite to match the example I gave you. But you don't need mod_rewrite to make a REST compliant API.
Using angular-resource module
Now that our API scheme is set and follows the REST+CRUD principles, we can exploit the full potential of the angular-resource module.
We can create a client side representation(interface) of "Friend".
//Custom actions
var actions = {
update: {
method: 'PUT'
}
}
var friendUrl = "/api/friend/:id"; // should be obtained by inspecting the API iteself, usually the parent collection.
var Friend = $resource(friendUrl, {id: '#id'}, actions);
To get a friend we would issue a GET request (and specify it's id);
Friend.get({id: 1}).$promise.then(
function (response) {
//console.log(response);
}
);
DELETE and PUT requests(which we created and called update) are basically the same thing. $resource also supports retrieving collections using the object's method query. You can use that to retrieve the collection of friends.
Please notice that I'm using a hardcoded URL for simplicity
request is just setting up your endpoint. You need to actually call some method on it, e.g. request.get({id: 1}).$promise; or request.query({term: 'test'}).$promise;
I'm trying to figure out a way to cache my knockoutJS SPA data and I've been experimenting with amplifyJS. Here's one of my GET functions:
UserController.prototype.getUsers = function() {
var self = this;
return $.ajax({
type: 'GET',
url: self.Config.api + 'users'
}).done(function(data) {
self.usersArr(ko.utils.arrayMap(data.users, function(item) {
// run each item through model
return new self.Model.User(item);
}));
}).fail(function(data) {
// failed
});
};
Here's the same function, "amplified":
UserController.prototype.getUsers = function() {
var self = this;
if (amplify.store('users')) {
self.usersArr(ko.utils.arrayMap(amplify.store('users'), function(item) {
// run each item through model
return new self.Model.User(item);
}));
} else {
return $.ajax({
type: 'GET',
url: self.Config.api + 'users'
}).done(function(data) {
self.usersArr(ko.utils.arrayMap(data.users, function(item) {
// run each item through model
return new self.Model.User(item);
}));
}).fail(function(data) {
// failed
});
};
This works as expected, but I'm not sure about the approach I used, because it will also require extra work on the addUser, removeUser and editUser functions. And seeing as I have many more similar functions throughout my app, I'd like to avoid the extra code if possible.
I've found a way of handling things with the help of ko.extenders, like so:
this.usersArr = ko.observableArray().extend({ localStore: 'users' });
Then use the ko.extenders.localStore function to update the local storage data whenever it detects a change inside the observableArray. So on init it will write to the observableArray in case local storage data exists for users key and on changes it will update the local storage data.
My problem with this approach is that I need to run my data through the model and I couldn't find a way to do that from the localStore function, which is kept on a separate page.
Has any of you worked with KO and Amplify? What approach did you use? Should I use the first one or try a combination of the two and rewrite the extender in a way that it only updates the local storage without writing to the observableArray on init?
Following the discussion in the question's comments, I suggested to use native HTTP caching instead of adding another caching layer on the client by means of an extra library.
This would require implementing a conditional request scheme.
Such a scheme relies on freshness information in the Ajax response headers via the Last-Modified (or E-Tag) HTTP headers and other headers that influence browser caching (like Cache-Control: with its various options).
The browser transparently sends an If-Modified-Since (or If-None-Match) header to the server when the same resource (URL) is requested subsequently.
The server can respond with HTTP 304 Not Modified if the client's information is still up-to-date. This can be a lot faster than re-creating a full response from scratch.
From the Ajax request's point of view (jQuery or otherwise) a response works the same way, no matter if it actually came from the server or if it came from the browser's cache, the latter is only a lot faster.
Carefully adapting the server side is necessary for this, the client side on the other hand does not need much change.
The benefit of implementing conditional requests is reduced load on the server and faster response behavior on the client.
A specialty of Knockout to improve this even further:
If you happen to use the mapping plugin to map raw server data to a complex view model, you can define - as part of the options that control the mapping process - a key function. Its purpose is to match parts of your view model against parts of the source data.
This way parts of the data that already have been mapped will not be mapped again, the others are updated. That can help reduce the client's processing time for data it already has and, potentially, unnecessary screen updates as well.
Not the easiest issue to put into a title.
Anyhow, my app is built on nodejs/expressjsand has an API set up for the url:
EDIT: The current code I'm using is:
$scope.updateProduct = $resource('/api/updateProduct/:product/:param/:value',{},{
query: {method:'GET'},
post: {method:'POST'},
save: {method:'PUT', params: {brand: '#brand', param:'#param', value:'#value'}},
remove: {method:'DELETE'}
});
$scope.updateProduct.save({
product : $scope.post._id,
param: 'likes',
value: $scope.user._id
});
At present it calls /api/updateProduct instead of /api/updateProduct/<product>/<param>/<value> like it's supposed to / like it does when I perform $scope.updateProduct.get().
In my console I see (as an example):
PUT /api/updateBrand/Quay%20Eyewear%20Australia/userTags/sunglasses,%20classic 200 30ms - 2.31kb
However, the API isn't actually accessed/nothing happens. Interestingly, if I go to localhost:5000/api/updateBrand/Quay%20Eyewear%20Australia/userTags/sunglasses,%20classic in my browser, it posts the correct data and updates the product in my database, so it's definitely an error with the way the $resource is being called.
As you can see in ngResource docs, $resource receive 3 parameters:
$resource(url[, paramDefaults][, actions]);
You are passing your action as a parameter.
The correct version would be:
$scope.updateProduct = $resource('/api/updateProduct/:product/:param/:value',{}, {'save':{method:'POST'}});
Note that it isn't even necessary, because when you use $resource you already create the default methods:
{
'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
First of all, your have defined the save() to have a parameter called "brand", but in your url template and in the call to save(), you are using "product". I guess it's a typo.
You say when you are using browser to visit the url, it works well; but when angular is make a PUT request to the same url, nothing is happening. This indicates that your backend is configure to process only GET requests for this particular url pattern. Therefore, you need to make sure that your backend is accepting PUT requests.
I was struggling with this issue and was able to pass parameters to the resource by doing the equivalent call below (notice the '$' before 'save').
$scope.updateProduct.$save({
product : $scope.post._id,
param: 'likes',
value: $scope.user._id
});
I also did not define a 'save' method in the resource. According to Angular docs:
"Calling these methods (meaning non-GET methods) invoke $http on the url template with the given method, params and headers. When the data is returned from the server then the object is an instance of the resource type and all of the non-GET methods are available with $ prefix. This allows you to easily support CRUD operations (create, read, update, delete) on server-side data."