First of all I want to mention that I have been digging around a lot for this. I am unable to find a simple and straight forward answer even in the docs. (Call me dumb if you will, in case it IS mentioned in the docs! I can't seem to find it anyway.)
The thing is, I want to make a PUT request to a URL of the form
app.constant('URL_REL_VENDOR_PRODUCTS', '/api/vendor/:vendorId/products/:productId');
But I do not want to put the vendorId parameter in the request payload. My service layer looks something like this:
services.factory('VendorProductService', function($resource, UserAccountService, URL_BASE, URL_REL_VENDOR_PRODUCTS) {
return $resource(URL_BASE + URL_REL_VENDOR_PRODUCTS, {
vendorId: UserAccountService.getUser().vendorId,
id: '#id'
}, {
update: { method: 'PUT' }
});
});
I know that instead of the vendorId: UserAccountService.getUser().vendorId I could have written something along the lines vendorId: '#vendorId' but then that pollutes my payload doesn't it?
I don't want to keep the mechanism I am already using in the example as the mechanism does not work when you switch accounts i.e.,if the UserAccountService.getUser() is updated. Basically I'm having to reload the entire page to get the service initialized again.
In short, the question is, as the title suggests, how do I set the path parameter vendorId without using a service like the one in the snippet and also without modifying the payload?
Make the parameter value a function:
services.factory('VendorProductService', function($resource, UserAccountService, URL_BASE, URL_REL_VENDOR_PRODUCTS) {
return $resource(URL_BASE + URL_REL_VENDOR_PRODUCTS, {
vendorId: function () {
return UserAccountService.getUser().vendorId
},
id: '#id'
}, {
update: { method: 'PUT' }
});
});
From the Docs:
paramDefaults (optional)
Default values for url parameters. These can be overridden in actions methods. If a parameter value is a function, it will be executed every time when a param value needs to be obtained for a request (unless the param was overridden).
-- AngularJS $resource API Reference
Related
I am facing a very weird case in my angularjs app. In a factory, the following code works properly:
$http.put(apiBase + 'delete?id='+orderId);
Which obviously connects to an api to perform a PUT operation (it is called "delete" here but it actually only updates a flag in the record).
But the same code, when written this way, does not work:
$http.put(apiBase + 'delete', {
params: {
id: orderId
}
}
);
Which is funny, because I am using the exact same syntax in some other factories to hit similar APIs and they work!
I believe this is because the second argument of $http.put() is a data object, not a config object. You are passing a config object as the second parameter.
You could try:
$http.put(apiBase + 'delete', null, {
params: {
id: orderId
}
}
);
https://docs.angularjs.org/api/ng/service/$http#put
When using $http.put, you don't need to wrap your data in the config object. You can pass the data object directly, and then omit the third parameter:
$http.put(apiBase + 'delete', { id: orderId });
Your other factories probably work with the syntax stated in your question because you are making $http.get or $http.delete requests.
I have found that this slightly-different API for the various "shortcut" methods to be confusing enough that I almost think it's better to avoid them altogether. You can see the differences from the documentation where get and delete have two parameters:
get(url, [config]);
delete(url, [config]);
and most of the other shortcut methods have three:
post(url, data, [config]);
put(url, data, [config]);
Note that the [config] object is defined further up on that documentation page, which is where it defines that "params" property:
params – {Object.} – Map of strings or objects which
will be serialized with the paramSerializer and appended as GET
parameters.
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).
I'm trying to mock a $http get request with parameters. Using the actual $http service we could do something like this
$http.get(url, {params : { articleId:10, id : 1}})
.success(function (response) {
defer.resolve(response.data);
});
Can we do the same with $httpBacked?
$httpBackend.when('GET', url, { params: { articleId: 10, id : 1}})
.respond({data: areas});
The obvious unpleasant alternative is writing out the full expected url which I am hoping I don't have to do as this feels neater.
Thanks
I had forgotten about this question, unfortunately the answer is that you have to specify the full get url with parameters
....just incase someone stumbles across this with the same problem
Apparently the only thing that can be passed parameters in a regular expression. According to the documentation of angular only four parameters can be passed, method, url, data, and headers.
Simple (seeming) question - I'm trying to do a simple sanity check in my AngularJS controller to make sure that my $resource is actually instantiated as such. It's a largish app, but for example:
.factory('AccountSearchService_XHR', ["$resource", function($resource) {
var baseUrl = "http://localhost\\:8081/api/:version/accounts/:accountNumber";
return $resource(baseUrl,
{
version: "#version",
accountNumber: "#accountNumber"
},
{
get: {method: 'GET', isArray: false}
});
}]);
Then later, in controller:
$scope.accountObj.currentAccount = AccountSearchService_XHR.get({
version: "v1",
accountNumber: "1234"
},
function(result) {... etc etc});
The call to my API works fine, everything returns data like I expect - but I'd like to test to see if $scope.accountObj.currentAccount is a Resource before trying to make the .get call (notice the super important capital "R").
When I inspect the object $scope.accountObj.currentAccount in chrome debugger, it looks like:
Resource {accountHolderName: Object, socialSecurityNumer: null, birthDate: "05/14/1965", maritalStatus: ...}
Because of some complexity in my setup though, occasionally it gets overwritten as a normal object (typeof returns "object"), but inspecting it in debugger confirms it lost its Resource status.
So - does anyone know of a way to test whether it is a $resource? Almost like typeof $scope.accountObj.currentAccount returns "Resource"? Or perhaps a better best practices way to ensure that things are connecting up all proper and respectable-like?
All the SO articles I have seen when searching revolve around actual Jasmine testing.
Thanks in advance.
#tengen you need to have injected the type you want to check against, instead of $resource.
All resources are instances of the "class" "Resource", but that's a function that's defined inside of the factory method of the $resource service, so you have no outside visibility to use it with the instanceof operator.
However, you're wrapping that $resource creating with your own custom type, AccountSearchService_XHR, and that's what you need to make the check against.
You need AccountSearchService_XHR to be injected in your code and then perform myRef instanceof AccountSearchService_XHR and that will be === true.
Digging up an old question my intern just had. The simple solution is:
if ($scope.accountObj.currentAccount instanceof AccountSearchService_XHR)
return 'This is a AccountSearchService_XHR Resource';
else
return 'This is not a AccountSearchService_XHR Resource';
which with proper names (Users being a $resource) and real case scenario should lead you to write something like this:
if (!(this.user instanceof Users))
this.user = new Users(this.user);
this.user.$update();
Check it via instanceof yourVariable === "Resource". Because Resource is an object the type will always return as an Object, but if you check that it's an instance of the Resource "class" that should work just fine.
The declaration of the User resource would be something like:
factory('User', function($resource) {
return $resource('/api/user/:userId.json', {}, {
put: {method:'PUT', params: {userId:'#id'}},
});
})
As you can see the -default- parameter for the PUT method is the id attribute within the resource.
if you would like to test:
httpBackend.expectPUT('api/user/1.json').respond(200);
userResource.put();
httpBackend.flush();
I keep getting a failure in the test cause the actual URL that it's being generated is: 'api/user/.json'. The id attribute is not being included in the URL.
It makes sense because I haven't specified the id attribute to the mock object, I didn't because I don't know how to do it.
Thanks in advance.
The path should start with '/', and you need to pass in an ID to make the path match with what is generated in your code. The URL match is string match, so you need to guarantee the URL you expect to hit is exactly same as what is generated.
httpBackend.expectPUT('/api/user/1.json').respond(200);
userResource.put({id:1});
httpBackend.flush();