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;
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 developing a simple Facebook app which will basically just allow users to POST statuses, photos, etc. - this is just an exploratory project at the moment.
I have been trying to POST statuses with varying privacy levels and have been able to POST with custom privacy successfully, however, when I try to tag a friend in the POST headers I get an oAuth exception error response.
My scope appears to include everything necessary to do what I want, but I may be missing something here.
"...&scope=email,user_groups,user_photos,read_friendlists,manage_friendlists,publish_stream,read_stream"
Here's my JS code:
FB.api(
"/me/feed",
"POST",
{
"message": $('textarea').val(),
"tags": $('input').val(),
"privacy" : {'value': 'CUSTOM','allow': $friend_list.val() }
},
function (response) {
if (response && !response.error) {
console.log('response is: ');
console.log(response);
}
else{
console.log('oops something went wrong here!');
console.log(response);
}
}
);
As the SDK docs are not really that informative, I am not really sure what the purpose of place, which I apparently have to include:
Comma-separated list of user IDs of people tagged in this post. You
cannot specify this field without also specifying a place.
I have tried setting "place" as a variety of values but I'm not sure what I am doing wrong. I have deliberately omitted it from the example code above by the way.
Thanks in advance
-- EDIT --
As requested, here is an example list of the headers being posted:
message:this is an example post
method:post
privacy:{"value":"CUSTOM","allow":"123456789012345"}
The above works correctly (given the correct user ID's that is), i.e. a status is created and it is only viewable by a single user/friend, but once I include "tags" it throws an oAuth error with little to no info or explanation. The reason I am trying to "tag" friends by the way is that I want people to be notified of a POST rather than just having access to it.
message:this is an example post
method:post
privacy:{"value":"CUSTOM","allow":"123456789012345"}
tags:123456789012345
I have also tried to set the "place" header, but again I have little understanding of its purpose so I may be supplying an incorrect value or something like that.
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."
My app is Backbone.js for client-side, Express.js for back-end.
I have problems with syncing with all parts of my API, using the backbone model and collection(they use urlRoot: "/users").
I'm allowed to use only GET or POST, no PUT or DELETE.
*I'm not allowed to use more models*
Not allowed to use jQuery ajax
My API
add new user:
I need to make a POST to /users with JSON of new data. So I did it just fine with - this.model.save({new data...})
list all users:
My API for that, responses to GET /users, with the right handler - so, this.collection.fetch() - works fine.
Log-In:
My API accepts POST to /users/login for that. How can I add a function "logIn" to my model, that will use custom sync/pass options.url to sync - or any other way - that will POST to /users/login ?
Log-Out:
API accepts POST to /users/logout - how to send this request using my backbone model ?
User By ID:
Same question here for GET /users/:id
Update user:
POST /users/:id - same question again.
--- So actually, the question in short is ---
What is the best way (or the most "right"), to implement methods of a backbone model, that are similar to "model.save()" - but just POST/GET to a bit different path then urlRoot ?
You probably have a couple options here. One would be structuring your models in a way that supports the urls you want. For instance, have a User model and a Session model that deal with updating the user and managing the logged in state separately.
The other thing you should probably do is to use the url method in your models.
Something like this in your User model. (Note: using urlRoot instead of url here is identical, but this is the correct approach for anything more complicated that is needed in the url)
url : function() {
var base = "/users/";
if(this.isNew()) {
return base;
} else {
return base + this.get("id");
}
}
You could extend this same concept to work in your Session model for handling logout vs login based on if the Session is new or not.
Update:
If you must use the same model, the best thing would be to totally bypass the Backbone.sync method and write a custom AJAX call with success/error handlers that know how to clean things up.
login : function() {
$.post("/users/login", {success: function (response) {
// Update user as needed
}, error: function (xhr, response) {
// Handle errors
}
}
}
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