Say, I have the following resource:
angular.module('app.resources.demo-resource', ['ngResource'])
.service('demoResource',
function ($resource) {
return $resource('/api/path', { }, { put: { method: 'PUT' } });
});
and I want to invoke the put request, using promises later.
How do I do it correctly?
newResource.$put().then(...) or newResource.put().then(...)?
Does both variants exist and what is the difference between them?
When you define a "non-GET" action (basically, one with a method that has a request body: PUT, POST, PATCH), ngResource will automatically create corresponding actions/methods on every instance of that resource (prefixed by $). This is just for convenience.
Basically, you have two ways to call such an action:
<Resource>.action([parameters], postData, [success], [error])
or
<instance>.$action([parameters], [success], [error])
The benefit of the latter is that you don't need to specify the postData (it's the <instance> object that acts as the data).
With promises: (To the answer of #ExpertSystem)
<Resource>.action([parameters], postData).$promise
or
<instance>.$action([parameters])
is automatically a promise. The first approach looks more stable in practice. (Angular resolved an instance to the wrong resource after I did _.extend({}, resource))
Related
We will soon be refactoring our code against the Angular Style Guide. The guide itself is great (and can be found slightly modified all over the interwebs), but no one mentions how $resource fits into a factory, or any reasons why it might have been left out. One guide says to use $resource over $http where you can, but then doesn't add it into their style for factories :/.
I remember reading in lots of places that $resource was better and that's why I started to use it, but now I'm forgetting why and wondering if that is still true - especially given the resource object at the bottom of this post. There are some opinions (Papas own, and again) about $resource (not?) being great, but that's another issue that I'm re-checking.
So, assuming we want to use $resource and given this sample code below, where does $resource fit in so that it adheres to the reasoning behind the styles in the guide? Also, if your answer is "It doesn't. The style [subtly] recommends $http because bla, bla and bla.", then that would be a useful as well.
(function() {
'use strict';
angular
.module('myModule')
.factory('oneService', oneService);
predicateService.$inject = ['twoService', 'anotherService'];
/* #ngInject */
function oneService(twoService, anotherService) {
var service = {
doSomething: doSomething,
etc: etc
};
// pos 1 (it really only works here but can be LONG)
// var fancyResource = $resource('/path/to/thing', '...');
// Ideally, this should be kept close to the top, right?
return service;
// pos 2 (here or below ////// is cleaner, but doesn't work)
// var fancyResource = $resource('/path/to/thing', '...');
////////////////
function doSomething() {}
// rest of functions here etc...
}
})();
Now, the only place that we use $resource (and maybe this is also incorrect) is within methods like doSomething(). At various points in the past, and even in various places in our code today, fancyResource is made public by the service and used directly from the controller: oneService.fancyResource.get(). I'm thinking this may be the intended use for $resource, but I'm not sure anymore.
Also, consider that one service might be quite large (never mind the fact that some of this should/could be broken into multiple resources; let's just pretend a resource object this size is likely and many verbs are needed):
var userResource = $resource(baseApiPath + 'users', {}, {
get: {
method: 'GET',
headers: utilityService.getHeaders('sampling'),
isArray: true,
transformResponse: function(response){
response = JSON.parse(response);
if(response.result){
return response.result.users;
}
return response;
}
},
getUserDetails: {
method: 'GET',
url: baseApiPath+'users/:userId',
params: {
userId: '#userId'
},
headers: utilityService.getHeaders('sampling'),
transformResponse: function(response){
response = JSON.parse(response);
if(response.result){
return response.result.user;
}
return response;
}
},
getUserByRole: {
method: 'GET',
url: baseApiPath+'users/roles/:roleId',
params: {
roleId: '#roleId'
},
headers: utilityService.getHeaders('sampling'),
},
getLoggedInUserData: {
method: 'GET',
url: baseApiPath + 'users/userData',
headers: utilityService.getHeaders('sampling'),
},
getGrantedAuth: {
method: 'GET',
url: baseApiPath+'users/applicationPermissions/userId/:userId/:applicationId/',
params: {
applicationId: '#applicationId',
userId: '#userId'
},
headers: utilityService.getHeaders('sampling'),
}
});
So, I think I've found my answer based on a few thoughts.
Firstly, I now realize that using a $resource like this is totally incorrect for two reasons. The first is that I was creating additional actions that required their own unique path. The whole point of a $resource is to make doing GET, PUT, POST, DELETE on a single REST resource easier. I was basically combining my resources because they appeared to be unified. For example, /users and /users/roles/:roleId should have been two different resources (and probably put into two different services to maintain the single responsibility style).
The second way I was using $resource wrong is actually because I wasn't really using the query, save, or delete methods that it supplies me with. I would just create another custom action for whatever I wanted to do. Sometimes this also included a unique URL like /users/:userId/delete, and that was because the API wasn't always a REST API. $resource is specifically designed for REST compliant APIs. Because it wraps $http and it can pass parameters to it, it's easy to fall into this trap. $resource is not intended to be a configuration for multiple $http uses.
So, now with that out of the way, here is how I would propose to include $resource into a factory, and still follow the style guide.
First, $resource should only be used with a true REST API. One where you only have/need one path, and only/mostly HTTP methods are used to interact with it. Also, because a factory is intended to represent and manage one kind of 'thing', interacting with the 'thing API', there should really only be one $resource per service. Extending the example, there would be a users service and a roles service; each with one $resource. There could then be another userRoleService that utilizes them both, and doesn't actually do any $resource stuff on its own. Something like that, anyway.
This being the case, the $resource config would actually be significantly shorter than what I was originally posting. Since it's smaller, we can treat it more like a variable declaration and put it above the service object that we create.
(function() {
'use strict';
angular
.module('myModule')
.factory('oneService', oneService);
predicateService.$inject = ['anotherService', '$resource'];
/* #ngInject */
function oneService(anotherService, $resource) {
// this resource is unlikely to get longer than this
var userResource = $resource('http://api.com/users/:userId', {
userId: '#userId'
});
// and we're still able to see all bindables at the top
var service = {
doSomething: doSomething,
etc: etc
};
return service;
////////////////
function doSomething() {
// and in a service method, we can use the resource like this,
userResource.query().$promise
.then(function(response){...})
}
function doSomethingElse() {
// or we could use the $resource in a way that would make
// chainable with another .then() in the calling method.
var load = userResource.query();
// do some internal stuff when we get the response
load.$promise
.then(function(response){...});
// then return the $resource object, or load.$promise
// so that another .then can be chained afterwards.
return load;
}
// rest of functions here etc...
}
})();
Anyway, that's the answer that I came up with. I hope this helps some of you who came here looking for what I was looking for (and couldn't easily find).
In ngResource action I can specify custom request headers. However I need to set the headers at the time of calling the resource action.
The reason is I need paging and sorting data for the list query, and those need to be specified by custom headers (X-Order, X-Offset, and so on). This data can vary from call to call, so I cannot have them in the resource action definition.
Is there a way to pass headers while calling the action? (other than setting $http defaults)
Try Restangular service.
You can find there method: setFullRequestInterceptor which may fit your needs
//From Documentation
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
return {
element: element,
params: _.extend(params, {single: true}),
headers: headers,
httpConfig: httpConfig
};
});
Examples: http://plnkr.co/edit/d6yDka?p=preview
I was going through the angularJS $Resource service documentation and I came across something strange.
(https://docs.angularjs.org/api/ngResource/service/$resource)
There are some custom action methods and you can also define your own. Then, these methods can be called with the following parameters:
HTTP GET "class" actions: Resource.action([parameters], [success], [error])
non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
But then, later in the example, a default action method:
'query': {method:'GET', isArray:true}
is called like this:
var CreditCard = $resource('/user/:userId/card/:cardId',
{userId:123, cardId:'#id'}, {
charge: {method:'POST', params:{charge:true}}
});
var cards = CreditCard.query(function() {
// GET: /user/123/card
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
var card = cards[0];
...
});
As you can see, the first parameter of the query method is the success callback function (at least, that's what I'm assuming this is) instead of the [parameters] param.
I'd expect:
var cards = CreditCard.query({}, function() { ... });
Am I missing something or can you simply omit the first parameter if you don't need it?
I know this is kind of a silly question but I want to make sure I'm not getting it wrong here...
The [] bracket in the Resource.action([parameters], [success], [error]) mean that those parameters are optional and could be omitted.
But that doesn't tell what will happen if only a single callback is passed as a parameters and you would think:
Will it be considered as a [parameters] or [success] or [error], since those are all optional, not even count the postData for a non-GET action.
I'm not sure if this is mentioned anywhere in the documentation. But to clear things up, lets explore the angularjs source code and you will find the exact logic for that.
https://github.com/angular/angular.js/blob/v1.2.21/src/ngResource/resource.js#L470
And here are all the combination that valid for a GET request.
// 4 arguments
params, data, success(), error()
// 3 arguments
params, data, success()
params, success(), error()
// 2 arguments
params, success()
success(), error()
// 1 argument
success()
params
Yes you can omit the first parameter if you don't want to pass any parameters with your request. As stated in the documentation:
The action methods on the class object or instance object can be
invoked with the following parameters:
HTTP GET "class" actions: Resource.action([parameters], [success],
[error])
The [] bracket notation implies that it is optional, so if "parameters" is not provided then the value expected are success and error callbacks.
I have standard angular $resource configured as such
angular.module('client.resources')
.factory('ProjectSubjectResource',['$resource',
function ($resource) {
release: {
method: 'DELETE',
isArray: false
}
});
}]);
and I am calling this method as
ProjectSubjectResource.release({projectId: projectId, subjectId: 0},{ subjectIds: subjectIdArray})
where subjectIdArray is array of objects:
[{subject1: 213123}, {subject2: 3131}]
However, body of request does not contain that array. I suspect that DELETE request is the problem, as renaming method call to e.g. PUT makes difference.
Can I allow body of DELETE request somehow?
Take a look at this answer.
The body of a request for a DELETE is ignored. You will have to use POST to do what you want, or describe the data you are sending with the URL.
UPDATE: DELETE requests can have a body since Angular 1.6.4; check denisazevedo's answer for additional info.
Starting on Angular 1.6.4, the hasBody action configuration was added.
You can now have:
deleteSomething: {
method: 'DELETE',
hasBody: true
}
hasBody - {boolean} - allows to specify if a request body should be
included or not. If not specified only POST, PUT and PATCH requests
will have a body.
Reference
In the "wire up a backend" demo code from the angularjs site they set up a db call. From what I can tell they're extending the update function in order to add some extra parameters needed by the mongolab api.
angular.module('mongolab', ['ngResource']).
factory('Project', function($resource) {
var Project = $resource('https://api.mongolab.com/api/1/databases' +
'/angularjs/collections/projects/:id',
{ apiKey: '4f847ad3e4b08a2eed5f3b54' }, {
update: { method: 'PUT' }
}
);
Project.prototype.update = function(cb) {
return Project.update({id: this._id.$oid},
angular.extend({}, this, {_id:undefined}), cb);
};
Then they call the update property like this:
$scope.save = function() {
$scope.project.update(function() {
$location.path('/');
});
I've tried using this code to build a demo app using a local development server so I've omitted extending the update property as I don't need the extra $oid parameter. What I do need is to specify that the update method should use PUT. My code is like this:
var Unit = $resource('http:/localhost/api/unit/:id', {id:'#Unit_Id'},
{'update': { method: 'PUT' }});
And then calling it like this:
$scope.save = function () {
$scope.unit.update(function () {
$location.path('/unitlist');
});
But What I've discovered is that the code only runs with a dollar sign in front of update like this:
$scope.save = function () {
$scope.unit.$update(function () {
$location.path('/unitlist');
});
So here are my questions:
In the demo code, where is "update" actually added to the Project variable? As a parameter in $resource or using prototype to extend Project?
Why is update undefined in my code unless I prefix $ when I call it?
"In the demo code, where is "update" actually added to the Project
variable? As a parameter in $resource or using prototype to extend
Project?"
Adding the following code to end of the factory() method:
console.log('Project.update=' + Project.update)
console.log('Project.prototype.update=' + Project.prototype.update)
return Project;
we see that Project.update is a function, and that Project.prototype.update is another/different function. So there are actually two update functions. Function Project.update is a (non-GET) "class" action, so it is called without a $ prefix. update() is a function defined on the object returned by $resource -- i.e., Project. Indeed, we see that function Project.prototype.update calls Project.update(...).
In the EditCtrl, when data is returned from the server, the project variable is a resource "instance" object (which is different from the resource "class" object), hence it has (non-GET) "instance" actions, which are prefixed with $. One of those actions is $update. Add the following log() to the EditCtrl function to prove that project.$update() exists:
Project.get({id: $routeParams.projectId}, function(project) {
console.log('project.$update=' + project.$update)
So now we have a third "update" function. However, in the demo code, they don't use this project.$update() function -- they use the function they defined on Project.prototype instead.
"Why is update undefined in my code unless I prefix $ when I call it?"
In your code, Unit.update should be defined (this is a resource "class" action), and when your data is returned from the server, then $scope.unit.$update becomes defined (this is a resource "instance" action). Unit is a class, $scope.unit is an instance.
In your code, you probably don't need to use Unit.update because I'm guessing you first Unit.get() your data, and then you assign the returned data to $scope.unit, and then you update it using the instance (not the class), hence you are updating with $scope.unit.$update() and not Unit.update(), which is the recommended approach.
Ok, here it is from the docs
The action methods on the class object or instance object can be
invoked with the following parameters: HTTP GET "class" actions:
Resource.action([parameters], [success], [error]) non-GET "class"
actions: Resource.action([parameters], postData, [success], [error])
non-GET instance actions: instance.$action([parameters], [success],
[error])
So, when you when you extend the class (prototype), call it without the $ like normal, but when you add an action as a parameter of $resource prefix $.
As far as I get it they extend the prototype with this piece of code:
Project.prototype.update = function(cb) {
return Project.update({id: this._id.$oid}, angular.extend({}, this, {_id:undefined}), cb);
};
Then they create an instance of the Project in line 30 of the project.js
$scope.project = new Project(self.original);
And then they can perform an update by simply calling:
$scope.project.update(...)
If you got rid of extending the prototype of your service, you can't call $scope.unit.update because there exist no property of unit that is called update.
I hope it helps :)