How do i get the get JSON data out of a $resource?
Currently all return values out of User.query and User.$save() are factory objects. I would like to be able to keep $scope.users as raw JSON so I can modify it and post the data to another URL.
myApp.factory("User", function($resource) {
return $resource("/api/users/:Id", { Id: "#Id"}, {
"update": {method:"PUT"},
"query": { method:"GET", isArray: true }
});
});
var users = User.query(function() {
$scope.users = users;
});
You can transform the response like this:
myApp.factory("User", function($resource, $http) {
return $resource("/api/users/:Id", { Id: "#Id"}, {
"update": {method:"PUT"},
"query": {
method:"GET",
isArray: true,
transformResponse: [function (data, headersGetter) {
return [{ result: JSON.parse(data) }];
}].concat($http.defaults.transformResponse)
}
});
});
Related
It's been a while since I've used $resource for managing my service calls.
For some reason, all my calls are working ok and reaching my REST end-points, basically /api/profile and /api/profile/:id.
But for some reason, my put returns as 404.
Anyone have an Idea of what may be going on.
Thanks and Cheers!
'use strict';
angular.module('chainLinkApp')
.config(['$stateProvider', function($stateProvider){
$stateProvider
.state('profile', {
url:'/profile/:id',
templateUrl:'views/profile.html',
controller:'ProfileController',
controllerAs:'profile'
});
}])
.controller('ProfileController',['$scope', '$http', 'profileFactory', function($scope, $http, profileFactory){
$scope.updateMode = false;
$scope.comments = profileFactory.getProfiles.go().$promise.then(function(response){
$scope.comments = response;
});
$scope.getProfile = function(commentId){
$scope.comment = profileFactory.getProfile.go({id:commentId}).$promise.then(function(response){
$scope.comment = response;
$scope.updateMode = true;
}, function(error){
return console.log('An error has occured', error);
});
};
$scope.addProfile = function(comment){
profileFactory.postProfile.go(comment).$promise.then(function(){
console.log('Your post was a success');
$scope.comment = {};
}, function(error){
console.log('There was an error: ', error);
});
};
$scope.updateProfile = function(comment){
profileFactory.updateProfile.go(comment._id, comment).$promise.then(function(response){
console.log('Your profile has been updated');
$scope.updateMode = false;
$scope.comment = {};
}, function(error){
console.log('There is an error: ', error);
});
};
}])
.factory('profileFactory', ['$resource', function($resource){
return{
getProfiles: $resource('/api/profile', {}, { go: { method:'GET', isArray: true }}),
getProfile: $resource('/api/profile/:id',{},{ go: { method: 'GET', params: { id: '#id' }}}),
postProfile: $resource('/api/profile', {}, { go: { method: 'POST' }}),
updateProfile: $resource('/api/profile/:id', {}, { go: { method: 'PUT', params: { id:'#id' }}})
};
}]);
The way of you are using $resource is strange, it should be like this:
.factory('UserService', function($resource) {
return $resource('/api/users/:id', {}, {
'create': { method: 'POST' },
'update': { method: 'PUT', params: { id: '#id'} }
});
})
Then you call the service like this: UserService.create(theUserobj, function(result) { ... })
I'm attemping to call a RESTful method via $resource as following:
Resource:
angular.module('secure',['ngResource']).factory('Vehicle', function ($resource) {
return $resource('/secure/vehicle/index', { id: '#id' }, {
query: {
method: 'GET',
isArray: true
},
delete: {
method: 'DELETE',
isArray: false,
url: '/secure/vehicle/delete/:id'
}
});
});
Then, from other service I inject the above factory and I call DELETE method in this way:
factory.delete = function (procedureId) {
var vehicle = new Vehicle();
vehicle.$delete({id: procedureId}, function () {
//success
deferred.resolve();
}, function (errResponse) {
// fail
console.log(errResponse);
});
return deferred.promise;
};
(Don't pay attention to deferred things, it doesn't work with or without it)
Unfortunately, I always get the same answer:
Remote Address:127.0.0.1:8080
Request URL:http://localhost:8080/secure/vehicle/delete/21
Request Method:DELETE
Status Code:422 Unprocessable Entity
The call itself is set up properly (secure/vehicle/delete/21). In fact, if I do the same, but instead of using $resource variable, using $http, everything works!
$http({
'method': 'DELETE',
'url': '/secure/vehicle/delete/' + procedureId,
'headers': {
'Content-Type': 'application/json'
},
'data': ""
})
.success(function () {
// success
})
.error(function (data, status) {
console.log(data.errors);
});
So, I guess something is missing in $resource-way, but what? Any help, it will be appreciated!
EDIT:
It seems it's a backend problem when it reads the entire url call. If I call DELETE resource, using $http, adding the data: "" as I show above, the backend initializes itself properly.
But if I try the $resource-way, the required params are pre-configured and that doesn't like to the backend, so I need to find the way to say to $resource how to add something like data: "", any ideas?
Test proof that it works:
angular.module('secure', ['ngResource']).factory('Vehicle', function($resource) {
return $resource('/secure/vehicle/index', {
id: '#id'
}, {
query: {
method: 'GET',
isArray: true
},
delete: {
method: 'DELETE',
isArray: false,
url: '/secure/vehicle/delete/:id'
}
});
});
angular.module('secure').factory('VehicleFactory', function(Vehicle, $q) {
var factory = {}
factory.delete = function(procedureId) {
var deferred = $q.defer();
var vehicle = new Vehicle();
vehicle.$delete({
id: procedureId
}, function(r) {
deferred.resolve(r);
}, function(errResponse) {
console.log(errResponse);
});
return deferred.promise;
};
return factory;
});
describe('VehicleFactory', function() {
var $httpBackend, VehicleFactory
beforeEach(module('secure'));
beforeEach(inject(function(_$httpBackend_, _VehicleFactory_) {
$httpBackend = _$httpBackend_
VehicleFactory = _VehicleFactory_
}))
it('deletes vehicle - vehicle.$delete()', function() {
var r = {
data: 'correct response'
}
$httpBackend.when('DELETE', '/secure/vehicle/delete/123').respond(r)
VehicleFactory.delete(123).then(function(response) {
expect(response.data).toBe(r.data)
})
$httpBackend.flush();
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation()
$httpBackend.verifyNoOutstandingRequest()
})
})
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
Some much cleaner way to write delete functionality without $q:
angular.module('secure').factory('VehicleFactory', function(Vehicle) {
var factory = {}
factory.delete = function(procedureId) {
return (new Vehicle()).$delete({
id: procedureId
})
}
return factory;
});
I have a HTTP based RESTful APIs
When i connect for example to www.domain.com/chiamate/ELSENWZ i got this result:
{
"TICKET": "155112-I",
"TICKET_2": "ATRE6463",
"ACCOUNT_NAME": "PIPPO",
"CUSTOMER_NUMBER": "AG5",
"PROBLEM_TYPE": "H",
"VENDOR": "ITALWARE-CON",
"DESCR": "HP 6300 PRO SFF",
}
I have implemented into AngularJS a service to use the rest api in this way:
var services = angular.module('ngdemo.services', ['ngResource']);
services.factory('ChiamataFactory', function ($resource) {
return $resource('/chiamate/:id', {}, {
show: { method: 'GET',
isArray: false, // <- not returning an array
transformResponse: function(data, headers){
var wrapped = angular.fromJson(data);
alert(JSON.stringify(wrapped, null, 4));
angular.forEach(wrapped.items, function(item, idx) {
wrapped.items[idx] = new Post(item); //<-- replace each item with an instance of the resource object
});
return wrapped;
} },
create: { method: 'POST' },
update: { method: 'PUT', params: {id: '#id'} },
})
});
because i want that when the controller use the service,
$scope.chiamata = ChiamataFactory.show({id: 'ELSENWZ'});
into result i need to add some extra properties.
The problem is that the service don't use the transformResponse
It is not possible to use the transformResponse to decorate the data with data from an asynchronous service
Try this pseudo-code
angular.module('myApp').service('MyService', function($q, $resource) {
var getResult = function() {
var fullResult = $q.defer();
$resource('url').get().$promise.then(function(data) {
var partialPromises = [];
for (var i = 0; i < data.elements.length; i++) {
var ires = $q.defer();
partialPromisses.push(ires);
$resource('url2').get().$promise.then(function(data2) {
//do whatever you want with data
ires.resolve(data2);
});
$q.all(partialPromisses).then(function() {
fullResult.resolve(data);
});
return fullResult.promise; // or just fullResult
}
});
};
return {
getResult: getResult
};
});
or you can use transformResponce with $http as described in the documentation
angular $http documentation
Ok, so I have this service that is dependent on another service value that the user can change in the app interface. Something like this:
app.service('Applications', ['$resource', 'URL',
function ($resource, URL) {
var applicationsResource = $resource(URL + '/applications/:id', { id: '#id' }, {
query: {
method: 'GET',
isArray: true,
transformResponse: function(body, header) {
var response = angular.fromJson(body);
return response.data.applications;
}
}
});
var applications = applicationsResource.query(function() {
applications.current = applications[0];
});
return applications;
}
]);
app.service('Users', ['$resource', 'URL', 'Applications',
function ($resource, URL, Applications) {
return $resource(URL + '/users/:id', { id: '#id' }, {
query: {
method: 'GET',
isArray: true,
headers: {
'User': Applications.current.username,
'Pass': Applications.current.password
},
transformResponse: function(body, header) {
var response = angular.fromJson(body);
return response.data.users;
}
}
});
}
]);
Example of working controller code:
app.controller('usersController', ['$scope', '$resource', 'URL', 'Applications',
function ($scope, $resource, URL, Applications) {
$scope.users = [];
$scope.reload = function() {
$scope.loading = true;
var usersResource = $resource(URL + '/users/:id', { id: '#id' }, {
query: {
method: 'GET',
isArray: true,
headers: {
'User': Applications.current.username,
'Pass': Applications.current.password
},
transformResponse: function(body, header) {
var response = angular.fromJson(body);
return response.data.users;
}
}
});
$scope.users = usersResource.query(function() {
$scope.loading = false;
});
/*
// after injecting Users, this is what I want to do, instead of what's above
$scope.users = Users.query(function() {
$scope.userTable.reload();
$scope.loading = false;
});
*/
};
$scope.$watch('Applications.current', function (newApplication, oldApplication, scope) {
if (newApplication && newApplication !== oldApplication) {
scope.reload();
}
});
}
]);
I want to replace that usersResource with my Users service, but that's where I'm stuck now.
The issue is that no matter what I do, the Applications.current on the Users service is always null. (I only make use of this service after making sure that Applications.current is not null on the controller)
If I move the resource directly to the controller, it works, but I want to move these away from the controllers.
Any tips on how to fix or improve this?
You should know that $resource is async and you call Users service before actually you got response from server and populated applications.current. This a reason why Applications.current is null into Users service.
In your case I would use Uses service into Applications:
app.service('Applications', ['$resource', 'URL', 'Users',
function ($resource, URL, Users) {
var applicationsResource = $resource(URL + '/applications/:id', { id: '#id' }, {
query: {
method: 'GET',
isArray: true,
transformResponse: function(body, header) {
var response = angular.fromJson(body);
return response.data.applications;
}
}
});
var applications = applicationsResource.query(function() {
applications.current = applications[0];
// call the Users
Users.query(applications.current) /**/
return /* ... */;
});
return applications;
}
]);
I have few resources written on AngularJS that access a Tastypie API. Everything works fine, except for a detail: tastypie always encapsulate the actual result inside a objects attribute on a JSON, example:
/api/v1/reminder/:
{
meta: {
limit: 20,
next: null,
offset: 0,
previous: null,
total_count: 3
},
objects: [{
category: {
color: "#999999",
id: 1,
name: "Groceries",
resource_uri: "/api/v1/category/1"
},
description: "",
due_date: "2010-10-16",
id: 1,
repeat: "weekly",
resource_uri: "/api/v1/reminder/1",
value: "-50"
}, {
category: {
color: "#999999",
id: 1,
name: "Groceries",
resource_uri: "/api/v1/category/1"
},
description: "",
due_date: "2010-10-17",
id: 2,
repeat: "weekly",
resource_uri: "/api/v1/reminder/2",
value: "-50"
}
}
It was wasy to fix using a callback to the get() call:
Reminder.get().$then(function (result) {
$scope.reminders = result.data.objects;
});
But I know result.resource is an actual Reminder instance.
.factory('Reminder', ['$resource', function($resource){
var Reminder = $resource('/api/v1/reminder/:id', {}, {
get: {
method: 'GET',
isArray: false
}
});
Reminder.prototype.TESTE = function () {console.log('asd');};
return Reminder;
}])
Now I need to implement behavior on my Reminder class, and I need every element on my meta.objects to be an instance of Reminder:
Reminder.get().$then(function (result) {
$scope.reminders = result.data.objects;
result.resource.TESTE(); // -> outputs 'asd'
o = result.data.objects[0];
o.TESTE // -> undefined, obvisously
i = new Reminder(o);
i.TESTE() // -> outputs 'asd'
});
So, how to I get angularjs to understand that every object on objects is the actual result so it behaves like a list of instances?
The workaround is to creating a new list iterating on the results creating the instances, but it's not optimal...
Suggestions?
Solution by #rtcherry:
As suggested by rtcherry, I used restangular
Configuring the reading of request data:
.config(['RestangularProvider', function(RestangularProvider) {
RestangularProvider.setBaseUrl("/api/v1");
RestangularProvider.setResponseExtractor(function(response, operation, what, url) {
var newResponse;
if (operation === "getList") {
newResponse = response.objects;
newResponse.metadata = response.meta;
} else {
newResponse = response.data;
}
return newResponse;
});
}])
Loading the reminders:
function RemindersCtrl ($scope, $rootScope, Reminder) {
$scope.reminders = Reminder.getList();
}
Adding my custom method to Reminder (not as clean as ngResource, but doable):
.factory('Reminder', ['Restangular', '$filter', function(Restangular, $filter){
var Reminder = Restangular.all('reminder');
var remainingDays = function () {
//do stuff
};
// adding custom behavior
Restangular.addElementTransformer('reminder', false, function (reminder) {
reminder.remainingDays = remainingDays;
return reminder;
});
return Reminder;
}])
Solution by #moderndegree:
I used pure ngResource:
var tastypieDataTransformer = function ($http) {
return $http.defaults.transformResponse.concat([
function (data, headersGetter) {
var result = data.objects;
result.meta = data.meta;
return result;
}
])
};
...
.factory('Reminder', ['$resource', '$http', function($resource, $http){
var Reminder = $resource('/api/v1/reminder/:id', {}, {
query: {
method: 'GET',
isArray: true,
transformResponse: tastypieDataTransformer($http)
}
});
Reminder.prototype.remainingDays = function () {
// doing stuff
};
return Reminder;
}])
My controller:
Transaction.query(filter).$then(function (result) {
$scope.days = [];
var transactions = result.resource;
resource[0].remainingDays(); // it works
});
If you wanted to avoid using an additional library, you should be able to do the following:
$resource('/api/v1/reminder/', {}, {
query: {
method: 'GET',
isArray: true,
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
return data.objects;
}
])
}
});
This will append your transform to $HttpProvider's default transformer.
Note: Correct me if I'm wrong on this one but I believe this feature requires v1.1.2 or greater.
You may want to try something like restangular.
There is some configuration needed to make that work. An example is here.
I ended up doing the following in order to preserve the meta object directly on the results returned by Reminder.query() (expanding on the answer from #moderndegree).
var Reminder = $resource('/api/v1/reminder/', {}, {
query: {
method: 'GET',
isArray: true,
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
return data.objects;
}
]),
interceptor: {
response: function(response) {
response.resource.meta = response.data.meta;
}
}
}
});
That allows you to get the meta value directly on the returned object:
var reminders = Reminder.query(...);
console.log(reminders.meta); // Returns `meta` as expected.
I think it would also be possible to do something similar inside the callback from Reminder.query since the response object is available there as well.