I've got a users list with delete button on /users url. My delete route looks like this:
app.route('/users/:userId')
.get(users.read)
.put(users.updateById)
.delete(users.delete);
app.param('userId', users.userById);
But problem is, that my delete button is calling delete on /users url, so I'm getting DELETE http://localhost:3000/users 404 (Not Found). How can I solve this problem? My controller remove() function you can see below. How can I pass '/user/' + user._id to it? User is removed correctly only from scope :(
$scope.remove = function(id) {
var user = $scope.users[id];
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Delete user',
headerText: 'Delete ' + user.displayName + '?',
bodyText: 'Are you sure you want to delete this user?'
};
modalService.showModal({}, modalOptions).then(function() {
if (user) {
user.$remove();
for (var i in $scope.users) {
if ($scope.users[i] === user) {
$scope.users.splice(i, 1); // remove item from scope
}
}
}
});
};
user service is basic from mean.js installation
angular.module('users').factory('Users', ['$resource',
function($resource) {
return $resource('users', {}, {
update: {
method: 'PUT'
}
});
}
]);
angular resources by default have the following methods: get, save, query, remove, delete. unless otherwise specified, they will not pass the param in the url the way you need them to. In this case, you need to specify the remove method and ensure that the param gets passed in the url.
angular.module('users').factory('Users', ['$resource',
function($resource) {
return $resource('users', {}, {
update: {
method: 'PUT'
},
remove: {
method: 'DELETE',
url: 'users/:id',
params: {id: '#_id'}
}
});
}
]);
this assumes your user objects have an _id property, which is common in the mean stack. You should probably setup get save and update the same way.
Related
A follow-up on a similar question I posted yesterday. I am trying to delete data from a table in Azure App service. This is my function in my Angular file.
function delName(user) {
//$scope.categories.push(user);
alert("about to delete. Action cannot be undone. Continue?")
$http.delete('https://test-evangelists-1.azurewebsites.net/tables/people', user, config)
.then(function (res) {
$scope.getNames();
});
}
Then I added an HTML button:
<button id="btn-del-evangelist" class="btn btn-default btn" ng-click="delName(user);">Delete User</button>
This is the value of my headers variable:
var config = {
headers: {
'Access-Control-Allow-Origin':'*',
'ZUMO-API-VERSION': '2.0.0'
}
};
But when I tried to run it, the console returns the following error:
which states that the header for ZUMO-API-VERSION must be specified.
Below is my code for GET and POST
GET:
function getNames() {
$http.get('https://test-evangelists-1.azurewebsites.net/tables/people', config)
.then(function (res) {
console.log(res);
$scope.people = res.data;
});
}
POST
function addName(user){
//$scope.categories.push(user);
alert("about to post!")
$http.post('https://test-evangelists-1.azurewebsites.net/tables/people', user, config)
.then(function (res) {
$scope.getNames();
});
}
Since I have already specified the header in my variable, I wonder what can be wrong here. Any help will be appreciated.
UPDATE:
I figured out that the Id must be appended to the URL before I can perform delete. However, I need to run a GET to retrieve the ID given the parameters but I am still encountering errors when getting the ID.
This is now my Delete function
function delName(user) {
alert("About to delete. Action cannot be undone. Continue?")
var retrievedId = "";
$http.get('https://test-evangelists-1.azurewebsites.net/tables/people', {
params: { name: user.name, location: user.location },
headers: { 'Access-Control-Allow-Origin': '*', 'ZUMO-API-VERSION': '2.0.0' }
})
.then(function (res) {
retrievedId = res.id;
alert(retrievedId);
});
$http.delete('https://test-evangelists-1.azurewebsites.net/tables/people/' + retrievedId, config)
.then(function (res) {
$scope.getNames();
});
}
Does anyone know what is wrong in the GET command when getting the ID?
UPDATE 2: I have written instead an Web Method (asmx) that will connect to SQL server to retrieve the ID passing the needed parameters. The ID will be returned as a string literal but in JSON format. Then I called JSON.parse to parse the string into JSON object then assigned the ID to a variable to which I appended in the URL. –
This is now my Delete function after I have written the Web Method.
function delName(user) {
var confirmres = confirm("You are about to delete this record. Action cannot be undone. Continue?");
var retrievedId = "";
if (confirmres == true) {
//get the ID via web service
$http.get('\\angular\\EvangelistsWebService.asmx/GetId', {
params: { name: user.name, location: user.location },
headers: { 'Access-Control-Allow-Origin': '*', 'ZUMO-API-VERSION': '2.0.0' },
dataType: "json",
contentType: "application/json; charset=utf-8"
})
.then(function (res) {
$scope.retData = res.data;
var obj = JSON.parse($scope.retData);
angular.forEach(obj, function (item) {
if (item.length == 0)
alert('No data found');
else {
//perform delete after getting the ID and append it to url
$http.delete('https://test-evangelists-1.azurewebsites.net/tables/people/' + item.id, config)
.then(function (res) {
$scope.getNames();
});
alert(item.id + ' deleted');
}
});
});
}
}
That is one way that I have learned on how to call HTTP DELETE on AngularJS. But I don't know if that is the optimal one. In any case, that works for me, unless there will be other suggestions.
$http.delete only has one parameter (config), not two (data, config).
Delete API
delete(url, [config]);
vs.
Post API
post(url, data, [config]);
To your updated problem:
To delete an item from your table, it appears the correct url is:
/tables/tablename/:id
Note the : before id.
I have a factory defined which returns a $resource:
myApp.factory('Region', function($resource) {
return $resource(baseUrl + '/templates/:templateId/regions/:regionId', null, {
query: {
method: 'GET',
isArray: false
},
update: {
method: 'PUT'
}
});
});
As you can see, a region is a subresource of a template, and I've defined the endpoint as /templates/:templateId/regions/:regionId.
My issue comes when I want to save a new region. How do I specify the templateId to save the region to? Here's my snippet:
$scope.save = function() {
if ($scope.mode === 'edit') {
// TODO
} else {
Region.save($scope.region, function(success) {
$state.go('app.templateList')
});
}
};
In every other resource I have I've just used Model.save($scope.model);, I don't know how to specify other URL parameters and the Angular docs don't seem to cover it.
According the docs, non-GET (e.g. PUT) methods accepts following arguments
Resource.save([parameters], postData, [success], [error]).
Where parameters is a path params and it is optional, postData – body of the request. If you want to provide templateId, just add it as first argument:
Region.save({templateId: 'id'}, $scope.region, function(success) {
$state.go('app.templateList')
});
I've faced similar dillema. I thought about some generic convention where to create subresource X eg as a new element of a collection owned by some resource Y I would do
POST /api/Y/<yId>/X
then to access collection of X owned by Y:
GET /api/Y/<yId>/X
However for modifying or deleting subresource we could access subresource directly:
PUT /api/X/<xId>
DELETE /api/X/<xId>
to achieve above we could use $resource definition as
Subresource = $resource('/api/:parent/:parentId/subresource/:id',
{ id: '#id' },
{
'update': { method:'PUT' } // this is because Angular lacks PUT support
});
then we can use it like
var subresourceList;
Subresource.query({parent: 'Y', parentId: parentId },
function(result) {
// handle result here
subresourceList = result;
});
and after modifying single subresource object we can save it using
var subresource = subresourceList[0];
subresource.someProp = 'newValue';
subresource.$update()
with earlier subresource definition the $update will do PUT directly to /api/X/<xId> which is reasonable whenever subresource X object in terms of being modified has nothing to do with its owning Y.
I've got a problem with service configuration. I want to display one user by this function:
$scope.findOne = function() {
$scope.user = Users.get({
userId: $stateParams.userId
});
};
But I am in trouble with User service :( I don't know, how should I change my the code to avoid angular error:
Error in resource configuration for action object. Expected response
to contain an array but got an {2}
Here is a code of my actual working service (without function findOne working of course:))
'use strict';
angular.module('users').factory('Users', ['$resource',
function($resource) {
return $resource('users', {}, {
update: {
method: 'PUT'
},
remove: {
method: 'DELETE',
url: 'users/:id',
params: {id: '#_id'}
}
});
}
]);
At a guess, I'd say your users API endpoint is expecting /users/:userId for GET requests. Your code at the moment will request /users?userId=nnn. You need to add an action for get with the ID in the URL, eg
return $resource('users', {id: '#userId'}, {
get: {
method: 'GET',
url: 'users/:id',
isArray: false
},
// etc
You can also make users/:id the default URL as long as it doesn't interfere with your other action configurations.
In my code I have:
var EntityResource = $resource('/api/:entityType', {}, {
postEntity: { url: '/api/:entityType/', method: 'POST' },
getEntity: { url: '/api/:entityType/:entityId', method: 'GET' },
putEntity: { url: '/api/:entityType/:entityId', method: 'PUT' },
deleteEntity: { url: '/api/:entityType/:entityId', method: "DELETE" },
getEntities: { url: '/api/:entityType/:action/:id', method: 'GET', isArray: true },
});
Then I am using the following to get data:
getProjects: function (
entityType,
deptId) {
var deferred = $q.defer();
EntityResource.getEntities({
action: "GetProjects",
entityType: entityType,
deptId: deptId
},
function (resp) {
deferred.resolve(resp);
}
);
return deferred.promise;
},
and the following to call getProjects:
entityService.getProjects(
'Project',
$scope.option.selectedDept)
.then(function (result) {
$scope.grid.data = result;
}, function (result) {
$scope.grid.data = null;
});
I think the intermediate function getProjects is not needed and I would like to directly use $resource.
Can someone give me some advice on how I could do this? I looked at the AngularJS documentation for $resource and it's not very clear for me.
$resource calls by default return empty arrays and then fill them up when the response is received. As mentioned in documentation
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
There are default 5 methods already defined on resource, get,save,query,remove,delete. You can directly call these rather than defining your own as you have done like postEntity, but the url template remains the same.
So once you define resource like this
var entityResource = $resource('/api/:entityType');
you can make calls like
var entity=entityResource.get({entityType:1},function(data) {
//The entity would be filled now
});
See the User example in documentation
If you want to return promise then you have to wrap the calls into your your service calls like you did for getProjects.
Update: Based on your comment, the definition could be
var entityResource = $resource('/api/:entityType/:action/:id')
Now if you do
entityResource.get({},function(){}) // The query is to /api
entityResource.get({entityType:'et'},function(){}) // The query is to /api/et
entityResource.get({entityType:'et',:action:'a'},function(){}) // The query is to /api/et/a
entityResource.get({entityType:'et',:action:'a',id:1},function(){}) // The query is to /api/et/a/1
Hope it helps.
$resource does expose $promise but it is on return values and subsequent calls.
My rest api accpets DELETE requests to the following url
/api/users/{slug}
So by sending delete to a specified user (slug) the user would be deleted. here is the service code:
angular.module('UserService',['ngResource']).factory('User', function($resource){
var User = $resource('/api/users/:id1/:action/:id2', //add param to the url
{},
{
delete_user: {
method: 'DELETE',
params: {
id1:"#id"
}
},
update: {
method: 'PUT',
params: {
id1:"#id"
}
}
});
return User;
});
I call the delete function via
user.$delete_user({id:user.id}, function(){}, function(response){});
However the request seems to be send to the wrong url.
/api/users?id=4
So the parameter is actually missing, as a result I get a 405 Method not allowed. Is there any chance to send the delete request in the style of my api?
params is an object of default request parameteres in your actions. If you want url parameters you have to specify them in the second parameter like this:
angular.module('UserService',['ngResource']).factory('User', function($resource){
var User = $resource('/api/users/:id1/:action/:id2', //add param to the url
{id1:'#id'},
{
delete_user: {
method: 'DELETE'
}
});
return User;
});
this works with either:
// user has id
user.$delete_user(function(){
//success
},function(){
// error
});
or
var data = {id:'id_from_data'};
User.delete_user({},data);
or
var params = {id1:'id1_from_params'};
User.delete_user(params);
I've made a plnkr-example - you have to open your console to verify that the DELETE requests are correct.
See parameterDefaults in the Angular resource documentation.
I had this problem for a while I was using a service to add / delete / update categories. While passing in params for get it worked fine but then when deleting it was giving me a ?id=1234 instead of api/resource/1234
I got around this by making the default param a string.
///Controller
Service.delete({categoryId:id}, function(resp){
console.log(resp)//whatever logic you want in here
});
//SERVICES
$resource('api/resource/:categoryId', {"categoryId":"#categoryId"}, {
query:{method:"GET"},
delete:{method:"DELETE"},
});
Should work and the resulting url will be, originally I had categoryId in the default params as a variable name.
api/resource/1234 etc
Just omit the '#' in the parameter
.factory('reportFactory', ['$resource', 'baseUrl', function ($resource, baseUrl) {
return $resource(baseUrl + '/keys/:id', {}, {
delete: { method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
params: {id: 'id'} }
})
}]);
this will give you:
http://localhost:8080/reports/api/keys/b8a8a8e39a8f55da94fdbe6c
without the question mark
If you want to delete a model, there's no need to add params (params does not work for DELETE anyway):
$resource('/users/:id').delete({id: user.id}, function(res) {
...
})
or
$resource('/users/:role/:id').delete({role: 'visitor', id: user.id});
I'm not sure if it's a bug of ngResource.