I have a $resource whose API will always return some data that needs to be cleaned up before going into the presentation layer. Specifically, it's .NET returning Date objects in the lovely '/Date(...)/' format.
I don't want to have to write a callback every time I call .query() or .get(). Is there some way to extend the resource with a callback that gets called upon REST methods that update the instance's properties, or by adding some sort of $watch that gets fired when the date property changes? Basically something that will happen for every instance of this $resource.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', function ($resource) {
var res = $resource('api/url/participants/:id', { id: '#id' });
// This obviously doesn't work, but something kinda like this?
res.prototype.$watch(this.FieldName, function(newVal, oldVal) {
if (needsCleaning(newVal.fieldName) {
this.FieldName = cleanupField(newVal);
}
};
});
Ah-ha, I found a way around it and will leave it here. In version 1.1.2 they added support for passing all the $http.config options to a $resource. Naturally, the CDN I'm using doesn't have a recent enough version of angular-resource.js, but switching CDNs solved that.
I just used the transformResponse option to modify the data as it comes back.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', '$http', function ($resource, $http) {
var res = $resource('api/url/participants/:id', { id: '#id' }, {
save: {
method: 'POST',
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
data.FieldName = yourDateParsingFunction(data.FieldName);
return data;
}
])
}
});
I'm just adding my transformer on to $httpProvider's transformResponse, which will do all the deserialization, etc.
An easy way to do this is to overwrite the existing $resource methods you want to do post-processing on with your own. See the code and comments below for an example.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', function ($resource) {
var res = $resource('api/url/participants/:id', { id: '#id' }, {
// create aliases for query and get to be used later
_query: { method: 'GET', isArray: true },
_get: { method: 'GET' }
});
// redefine the query method
res.query = function() {
// call the original query method via the _query alias, chaining $then to facilitate
// processing the data
res._query.apply(null, arguments).$then(function(res) {
var data = res.data;
// do any processing you need to do with data here
return data;
});
};
// redefine the method
res.get = function() {
// call the original get method via the _get alias, chaining $then to facilitate
// processing the data
res._get.apply(null, arguments).$then(function(res) {
var data = res.data;
// do any processing you need to do with data here
return data;
});
};
return res;
});
You'd use it the same way you're currently using Participant in your code, via Participant.query() or Participant.get(). The data you return in the chained $then handler will be used to resolve the promise returned by $resource.
The way I did it was by adding a service to the module:
angular.module('keeniolab', ['ngResource']).
factory('KeenIO',function ($resource) {
// factory implementation
}).service('KeenModel', function (KeenIO) {
var KeenSession = function () {
this.data = {};
};
KeenSession.prototype.fetch = function (query) {
var self = this;
KeenIO.get(query, function (result) {
self.data = result;
});
};
return new KeenSession();
});
Now you can simply monitor the collection:
$scope.$watchCollection(function () {
return KeenModel.data;
},
function (value) {
// code here
});
Keen.IO Resource Factory with Service Model
Related
I have the following 3 methods in my module.factory dataservice I am using Angular 1.5
getCannedJSON . This function works as intended and i would like the others to behave the same way. I copy and pasted the JSON i got from my webAPI in postman and put this in to the function. It returns an array of objects like i want.
getDataFromAPI. For some reason I cannot get this function to return the response. The console.log(response) has exactly the data I want aka the same data as getCannedJSON. Instead it returns a d {$$State: object} any idea how i could alter this code to change have it return in the same format as the getCannedJson method?
getDataFromApiWithDynamicUrl this is no different than the above method but it will take a dyanmic url for the web api. It workds fine minus it not returning an array list of json objects it instead returns the same $$State object.
I would like getDataFromAPI to return the same array of all the objects in the json request like getCannedJson does. Any ideas where I am messing up. Below is a screenshot of the two different types of objects they are returning via console.log I would like the data at the bottom to look like the data at the top.
The code for the dataService module factory is below
(function (module) {
'use strict';
DataService.$inject = ['$http', '$q'];
function DataService($http, $q) {
var getDataFromAPI = function () {
var returnthis;
return $http({ //this top level returns instead
url: "http://localhost:34183/api/quality/month",
dataType: 'json',
method: 'GET',
}).success(function (response) {
console.log("This Response shown below is pefect! but it wont return....");
console.log(response);
return (response);//This never returns
}).error(function(error){
console.log(error);
});
return returnthis;
};
var getDataFromApiWithDynamicUrl = function (pathurl) { // this is version 2 of the method i want to work where i can dynamically change the url to the path
return $http.get(pathurl);
};
var getCannedJSON = function ($http) {
return [{
"hockeyTeam": "Sharks",
"PlayoffRecord": {
"wins": "0"
},
},
{
"hockeyTeam": "Pengiuns",
"PlayoffRecord": {
"wins": "1"
},
}
];
};
return {
getDataFromAPI: getDataFromAPI,
getDataFromApiWithDynamicUrl: getDataFromApiWithDynamicUrl,
getCannedJSON: getCannedJSON
};
}
module.factory('DataService', DataService);
})(angular.module('MyCoolModule'));
below is the code where i call these methods to consume the JSON data in my controller.
(function (module) {
'use strict';
hockeyViewController.$inject = ['DataService'];
function hockeyViewController(DataService) {
var vm = this;
vm.headers = [
{ name: 'Hockey Team', key: 'hockeyTeam' },
{ name: 'Record', key: 'PlayoffRecord'}
];
vm.cannedData = angular.copy(DataService.getCannedJSON());
vm.getDataFromAPI = DataService.getDataFromAPI();
vm.getDataFromAPIwithCustomURL = [];
DataService.getDataFromApiWithDynamicUrl("http://localhost:34183/api/quality/month").then(function(response){
console.log("this response should work - and it does it in the right format");
console.log(response.data);// this looks perfect
vm.getDataFromAPIwithCustomURL = response.data;
return response.data;
}, function (error) {
console.log(error);
});
vm.testMonthResults2 = angular.copy(DataService.getDataFromApiWithDynamicUrl("http://localhost:34183/api/quality/month"));
console.log("canned json Data- works great");
console.log(vm.cannedData);// this works perfectly
console.log("this is the data results with dynamic url - returns wrong object the $$state ");
console.log(vm.getDataFromAPI);// returns $$state not array of objects
console.log(vm.getDataFromAPIwithCustomURL); // this returns [] which is wrong
console.log(DataService.getDataFromApiWithDynamicUrl("http://localhost:34183/api/quality/month"));
// this doesnt work either
}
function reportTabularViewDirective() {
return {
restrict: "E",
controller: hockeyViewController,
controllerAs: 'vm',
bindToController: true,
scope: {
},
templateUrl: "app/widgets/hockey-view.html"
};
}
module.directive('hockeyView', hockeyViewDirective);
})(angular.module('MyCoolModule'));
Can try this one
var getDataFromAPI = function () {
return $http({
url: "/api/quality/month", // try using relative path
dataType: 'json',
method: 'GET',
}).then(function(response) {
console.log(response);
return respose.data;
}, function(error) {
console.log(error);
return [];
});
};
But better to use like: service return only promise and in controller use then function to handle response
In service:
var getDataFromAPI = function() {
return $http.get('/api/quality/month');
};
in controller:
DataService.getDataFromAPI().then(function(response) {
console.log(response.data);
}, function(error) {
console.log(error);
});
You get a $promise object by calling DataService.getDataFromAPI(). You need to handle the $promise object to get the response.
DataService.getDataFromAPI().then(function(response) {
// console.log(response);
})
The same applies when you getDataFromApiWithDynamicUrl() function.
For more info, see doc:
$http
$q
As you may know, AngularJS $http service is allowing to call it with/out specific function, for ex:
$http(req).then(function(){...}, function(){...});
$http.get('/someUrl', config).then(successCallback, errorCallback);
I would like to get some more information about the way I can implement it on my factory and generally in JS.
Functions are Objects in JavaScript. This means that you can assign other properties and functions on a function.
function foo(){
//do something
}
foo.bar = function(){
//do something else
}
As it was mentioned above you can implement what you want using Angular's '$resource'. Here is an example of how it can be used:
app.service('testResource', ['$resource', function ($resource) {
var apiBaseUrl = 'http://test-backend/api';
var testResource = $resource(
apiBaseUrl + '/test/'
{},
{
'query': {
method: 'GET',
isArray: true
}
}
);
this.getAll = function () {
return testResource
.query()
.$promise
.then(function (data) {
var tests = [];
angular.forEach(data[0], function (value) {
tests.push(value);
});
return tests;
});
};
}]);
Then inject it in Controller (or wherever) and call it:
testResource.getAll().then(
function (data) {
$scope.tests = data;
}
);
You can also implement other methods such as POST, PUT, DELETE.
I'm trying to cache response from $http into an object for a session in angular, so once the initial call has been made, every other call to service.getCategories() (e.g), will get the data from the object rather than to the api.
The service is being resolved at the route, but there is authentication, which will redirect to another route - calling service.getCategories() again.
I'm attempting this by setting an init variable on call, then all other calls will direct to the populated object - but it seems to reset the service somehow, and the returned object gets populated twice, so there's double of everything. See below:
var $ = require('jquery');
module.exports = angular.module('app.common.services.category', [])
.factory('categoryService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) {
// API Parameters
var deferred = $q.defer();
// Services
var Categories = {
init: false,
categories: [],
index: 0,
// Retrieve all data on load.
// Loaded into an array for all other services
// to use after init.
getCategories: function(page) {
if(!Categories.init) {
$http.get('api/core/get_category_index')
.then(function(result) {
var data = result.data.categories;
$.each(data, function(i, category) {
category.index = Categories.index;
Categories.categories.push(category);
Categories.index++;
});
Categories.init = true;
return deferred.resolve(Categories.categories);
});
// Return promise once catgories is resolved
return deferred.promise;
} else {
return Categories.categories;
}
},
allCategories: function() {
return Categories.categories;
}
}
return Categories;
}]);
A problem with your approach is when the service function getCategories is called for the second time, the first time server request may not is resolved, causing a second call to the server. So you should move the init flag directly after the function call getCategories.
An other problem is that in your case you don't know whether the function will return a promise or an Array. I Suggest always returning an Array
module.exports = angular.module('app.common.services.category', [])
.factory('categoryService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) {
// API Parameters
var deferred;
// Services
var Categories = {
categories: [],
index: 0,
// Retrieve all data on load.
// Loaded into an array for all other services
// to use after init.
getCategories: function(page) {
if(!deferred) {
// replacement for intit flag
deferred = $q.defer();
$http.get('api/core/get_category_index')
.then(function(result) {
var data = result.data.categories;
$.each(data, function(i, category) {
category.index = Categories.index;
Categories.categories.push(category);
Categories.index++;
});
deferred.resolve(Categories.categories);
});
}
// always return a promise
return deferred.promise;
},
allCategories: function() {
return Categories.categories;
}
}
return Categories;
}]);
Maybe you can return the service itself with the promise. Then you could write everywhere something like:
myService.load().then(
function success(theService) {
theService.allCategories()
}
);
Now it doesn't matter anymore whether the service was loaded before or not
I want to create a find method that loops through an array returned by the $resource service in Angular.
If I have a service like so:
'use strict';
angular.module('adminApp').factory('ProductType', function($resource) {
var ProductType;
ProductType = $resource('http://localhost:3000/api/v1/product_types/:id.json', {
id: '#id'
}, {
update: {
method: 'PUT'
}
});
ProductType.find = function(typeName){
var types = this.query(),
typeObject = {},
self = this;
for(type in types) {
var result = types[type],
resultName = self.normalizeName(result.name),
if(typeName === resultName) {
typeObject = result;
}
}
return typeObject;
};
return ProductType;
});
I tried wrapping it all in a function and returning the function thinking it had something to do with it being async and I also tried nesting a callback in the query method but that just allowed me to modify the response and not actually return anything differently.
When I try and set the return value to $scope in the controller I get a blank object
The this.query() method would return an array which might not be filled until the this.query() method has got its results back from the server. You will need to do something like this to wait until the call to the server has completed. As this is sort of async you will need to return a promise from this method that is resolved when the initial query has completed and you have searched the results.
'use strict';
angular.module('adminApp').factory('ProductType', [
'$q',
'$resource',
function($q, $resource) {
var ProductType;
ProductType = $resource('http://localhost:3000/api/v1/product_types/:id.json', {
id: '#id'
}, {
update: {
method: 'PUT'
}
});
ProductType.find = function(typeName) {
var defer = $q.defer(),
types = this.query(),
self = this;
types.$promise.then(function () {
var result,
resultName,
typeObject,
type;
for(type in types) {
result = types[type];
resultName = self.normalizeName(result.name);
if(typeName === resultName) {
typeObject = result;
break;
}
}
defer.resolve(typeObject);
}, function (err) {
// the called failed
defer.reject(err);
})
return defer.promise;
};
return ProductType;
}]);
Taken from the angular docs https://docs.angularjs.org/api/ngResource/service/$resource
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. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data.
I have basically created a cache in my Angular service. If the REST call has already been made, the an array is returned, else it makes the REST call to get the needed info. Here is my service:
define(function(require) {
var module = require('portal/js/services/app.services');
return module.factory('providerService', function($resource) {
return {
requestingProviders: [],
getRequestingProviders: function() {
var that = this;
if(this.requestingProviders.length === 0) {
this.getResource().search({
role: 'REQUESTING_PROVIDER'
}, function(data) {
that.requestingProviders = data.providers;
return that.requestingProviders;
});
} else {
return that.requestingProviders;
}
},
getResource: function() {
return $resource('/api/v1/providers/:providerId', {providerId:'#id'}, {
search: {
method: 'GET',
headers: {
'RemoteUser': 'jhornsby',
'Content-Type': 'application/json'
},
params: {
limit: 2000,
organizationId : '0001194'
}
}
});
}
};
};
Here is an example of me accessing the service from a controller:
define(function(require) {
var module = require('portal/js/controllers/app.controller');
module.controller('AddPatientCtrl', function($scope, providerService) {
$scope.providers = providerService.getRequestingProviders();
};
return module;
});
When the REST call returns, it does not update my scope.providers variable in my controller. When I try to access it a 2nd time, then it works fine by accessing the array instead of calling out to the REST service. WHy won't my scope update in my controller during the REST call?
The prpblem is that you are calling an async get method
You'll have to assign the $scope in a callback function from the service (or in this case the factory) to make sure that it will take the desired value.
As you have said, in the first time $scope is assined to something that has not been yet resolved but in subsequent calls that parameter is resolved so $scope will get the proper value.
I hope it helped.