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
Related
I have this code in my post.serv.js and in my controller I want to execute the function delete.
"use strict";
app.factory('JnttPost', function ($resource) {
var PostResource = $resource('/api/post/:_id', {
_id: "#id"
}, {
update: {
method: 'PUT',
isArray: false
}
}, {
delete: {
method: 'DELETE',
isArray: false
}
});
return PostResource;
});
I already know how to get and update a post, for example in my createpost.serv.js
"use stric";
app.factory('JnttCreatePost', function ($http, $q, JnttPost) {
return {
createPost: function (newPostData) {
var newPost = new JnttPost(newPostData);
var dfd = $q.defer();
newPost.$save().then(function () {
dfd.resolve();
}, function (response) {
dfd.reject(response.data.reason);
});
return dfd.promise;
}
};
});
and in my newpost.ctrl.js
"use strict";
app.controller('CtrlNewPost',
function ($scope, $location, JnttIdentity, JnttNotifier, JnttCreatePost) {
var email = ...;
$scope.newPost = function () {
var newPostData = {...};
JnttCreatePost.createPost(newPostData).then(function () {
JnttNotifier.notify('success', 'The post has been created');
$location.path('/');
}, function (reason) {
JnttNotifier.notify('error', reason);
});
};
});
I can't realize how to perform the delete request, I can do with a $http
In my new controller for do deletePost() function I have this:
$scope.deletePost = function () {
var pwd = JnttIdentity.currentUser.hashed_pwd;
var postidd = {
password: pwd,
id: $scope.post._id
};
var config = {
method: "DELETE",
url: '/api/post/',
data: postidd,
headers: {
"Content-Type": "application/json;charset=utf-8"
}
};
$http(config);
$location.path('/');
};
This actually already do this stuff but I want to do this without the $http like the create request, How I can do this? How do I can edit this code below for do the request?
createPost: function (newPostData) {
var newPost = new JnttPost(newPostData);
var dfd = $q.defer();
newPost.$save().then(function () {
dfd.resolve();
}, function (response) {
dfd.reject(response.data.reason);
});
return dfd.promise;
}
In my routes.js in express I have this route:
app.delete('/api/post/', posts.deletePost);
You can either call delete on the $resource class you create (JnttPost) or call $delete on a post that's returned from the $resource class.
The $resource class already has get/save/query/remove/delete functions included so you don't need to add the delete (save is create/POST, so you need to include update with PUT).
Here's a sample of using your $resource class to call delete:
angular.module('test', ['ngResource'])
.factory('JnttPost', function ($resource) {
var PostResource = $resource('/api/post/:_id', {
_id: "#id"
}, {
update: {
method: 'PUT',
isArray: false
}
});
return PostResource;
})
.run(function(JnttPost){
JnttPost.delete({id: 123123123});
});
How to make angularjs $resource return an array of objects derived/prototyped from specified domain object?
Here is an example on http://plnkr.co/edit/AVLQItPIfoLwsgDzoBdK?p=preview that processes a set of Notes objects.
app.controller('MainCtrl', function($scope, NoteResource) {
$scope.name = 'World';
$scope.notes = NoteResource.query();
$scope.spellCheckAllNotes = function() {
angular.forEach($scope.notes, function(note) {
note.spellCheck();
});
}
});
The issue is that $resource returns array of Resources and not an array of Notes with Resource methods added to prototypes.
[solution shall follow "good" javascript practices]
Here is the completed plunker. Yes raw json is parsed to JSON object. It is using transformResponse as mentioned by Armando.
app.factory('NoteResource', ['$resource',
function($resource) {
var res = $resource('http://okigan.apiary.io/notes/:id', {}, {
query: {
method: 'GET',
params: {
},
isArray: true,
transformResponse: function(data, header){
//Getting string data in response
var jsonData = JSON.parse(data); //or angular.fromJson(data)
var notes = [];
angular.forEach(jsonData, function(item){
var note = new Note();
note.noteTitle = item.title;
notes.push(note);
});
return notes;
}
}
});
return res;
}
]);
Just to show title is not used from the raw resource, I modified title to noteTitle in Note and in html.
You can manipulate your data using transformResponse option in your resource service definition (make sure to set isArray to true):
angular.module('services', ['ngResource']).
factory("yourService", function ($resource) {
return $resource(
'/custom/url', {}, {
get: {
method: 'GET',
isArray: true,
transformResponse: function(data, headers){
//
// transform to array of objects
return data;
}
}
}
);
});
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 am adding a pair of actions to an AngularJS resource, but when I invoke the action, my transformRequest function is not getting called:
var _resource = $resource('api/NewItem/:id',
{ id: '#id' },
{
create: {
method: 'POST',
transformRequest: function (data, headersGetter) {
var result = JSON.stringify(data.productIntro);
return result;
}
},
update: {
method: 'PUT',
transformRequest: function (data, headersGetter) {
var result = JSON.stringify(data.productIntro);
return result;
}
}
});
If I add the function globally on the app, it works:
var newItemApp = angular.module('newItemApp', ['ngResource'])
.config(function ($httpProvider) {
$httpProvider.defaults.transformRequest = function(data)
{
if (data === undefined) {
return data;
}
var result = JSON.stringify(data.productIntro);
return result;
};
});
What I need to do is remove the root element from any POST or PUT action because the default model binding in Web Api does not bind a json object when that object has a named root.
transformRequest is supported since AngularJS 1.1.2. If you use early version, you need to add it to $httpProvider.
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.