I am using Angular resourse to get my data from an API, in this way:
var getAccountListPerUser = function () {
return $resource(uri, {}, {
get: {
headers: service.getDefaultHeaderRequest(),
method: 'GET',
transformResponse: function (data) {
var accountList = [];
try {
accountList = JSON.parse(data);
} catch (e) {
accountList = [];
}
return accountList;
},
isArray: true,
cache: true
}
}).get().$promise;
};
In my controller I have to use it and another two service functions defined in the same way.
var promiseResourcesAccountList = usrWebUserService.getAccountListPerUser();
promiseResourcesAccountList.then(function(result){
$scope.usersWithAccountsAndProfiles = result;
var filteredProfiles = [];
for (var account in result) {
...
}
$scope.filteredProfiles = filteredProfiles;
});
And:
var promiseResourcesEditUser = usrWebUserService.getResourcesUser(currentUser);
promiseResourcesEditUser.then(function (result) {
usrWebUserFactory.mapBasicPreferences($scope, result);
});
And then another very similar, this information loads data in three divs, but I want to show them only when all the three functions have completed correctly. I think I have to chain the result of the promises. How can I do that?
You can chain them like:
promiseResourcesAccountList.then(function(result){
///whatever processing
//return a promise
return promiseResourcesEditUser()
}).then(function(){
return anotherPromise();
}).then(function(){
//update scope here
});
alternatively, you could also use $q.all([promise1, promise2, promise3]).then(...);
#terpinmd is correct. Chaining promises is pretty simple. Say you have a service with a "getWidgets" that returns a promise, and you want to use the response from that service to call another service, "getWidgetOwners" that will return another promise :
Assumptions
getWidgets returns an array of widget objects.
getWidgetOwners accepts an array of ownerIds
How To:
service.getWidgets()
.then(function(widgets) {
return widgets.map(function(widget) { // extract ownerIds
return widget.ownerId;
});
})
.then(service.getWidgetOwners) // pass array of ownerId's to
.then(function(owners) { // the next service
console.log(owners);
});
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
I think I'm writing my promise incorrectly and I couldn't figure out why it is caching data. What happens is that let's say I'm logged in as scott. When application starts, it will connect to an endpoint to grab listing of device names and device mapping. It works fine at this moment.
When I logout and I don't refresh the browser and I log in as a different user, the device names that scott retrieved on the same browser tab, it is seen by the newly logged in user. However, I can see from my Chrome's network tab that the endpoint got called and it received the correct listing of device names.
So I thought of adding destroyDeviceListing function in my factory hoping I'll be able to clear the values. This function gets called during logout. However, it didn't help. Below is my factory
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
var deferredLoad = $q.defer();
var isLoaded = deferredLoad.promise;
var _deviceCollection = { deviceIds : undefined };
isLoaded.then(function(data) {
_deviceCollection.deviceIds = data;
return _deviceCollection;
});
return {
destroyDeviceListing : function() {
_deviceCollection.deviceIds = undefined;
deferredLoad.resolve(_deviceCollection.deviceIds);
},
getDeviceIdListing : function() {
return isLoaded;
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
var deferred = $q.defer();
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
_deviceCollection.deviceIds = _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
}
})
.value();
deferredLoad.resolve(_deviceCollection.deviceIds);
var deviceIdMapping = _.chain(_deviceCollection.deviceIds)
.groupBy('deviceId')
.value();
deferred.resolve(deviceIdMapping);
});
return deferred.promise;
}
}
}])
and below is an extract from my controller, shortened and cleaned version
.controller('DeviceController', ['DeviceFactory'], function(DeviceFactory) {
var deviceIdMappingLoader = DeviceFactory.getDeviceIdMapping('http://10.5.1.7/v1');
deviceIdMappingLoader.then(function(res) {
$scope.deviceIdMapping = res;
var deviceIdListingLoader = DeviceFactory.getDeviceIdListing();
deviceIdListingLoader.then(function(data) {
$scope.deviceIDCollection = data;
})
})
})
Well, you've only got a single var deferredLoad per your whole application. As a promise does represent only one single asynchronous result, the deferred can also be resolved only once. You would need to create a new deferred for each request - although you shouldn't need to create a deferred at all, you can just use the promise that you already have.
If you don't want any caching, you should not have global deferredLoad, isLoaded and _deviceCollection variables in your module. Just do
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
function getDevices(deviceIdsEndpoint) {
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
return RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
return _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
};
})
.value();
});
}
return {
destroyDeviceListing : function() {
// no caching - nothing there to be destroyed
},
getDeviceIdListing : function(deviceIdsEndpoint) {
return getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
return this.getDeviceIdListing(deviceIdsEndpoint)
.then(function(deviceIds) {
return _.chain(deviceIds)
.groupBy('deviceId')
.value();
});
}
};
}])
Now, to add caching you'd just create a global promise variable and store the promise there once the request is created:
var deviceCollectionPromise = null;
…
return {
destroyDeviceListing : function() {
// if nothing is cached:
if (!deviceCollectionPromise) return;
// the collection that is stored (or still fetched!)
deviceCollectionPromise.then(function(collection) {
// …is invalidated. Notice that mutating the result of a promise
// is a bad idea in general, but might be necessary here:
collection.deviceIds = undefined;
});
// empty the cache:
deviceCollectionPromise = null;
},
getDeviceIdListing : function(deviceIdsEndpoint) {
if (!deviceCollectionPromise)
deviceCollectionPromise = getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
return deviceCollectionPromise;
},
…
};
I'm a junior dev, so I might be missing something obvious, but I'm feeling a bit loony. I have a simple Angular webapp. I'm attempting to load a hash-dictionary of environment names that correspond to arrays of hosts. {development: ["dev.8090", "host.dev.9009"]} and then use that dictionary to find which host I'm currently on. I should be able to pass the location.host variable to the getEnv method and find the correlating key that will tell me which environment I'm in.
The dictionary loads, but when I try to access it inside of the getEnv method, it reverts to an empty object. Not undefined, mind you, but empty. Here's my code:
var app = angular.module('app', ['ngResource', 'ui.bootstrap', 'ui.router']);
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}]);
function AppController($scope, $http) {
window.MY_SCOPE = $scope;
$scope.env = "Local";
$scope.dict = {};
$scope.loadDict = function() {
$http.get('api/call/').
success(function(data){
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
}).error(function(data){
console.error(data);
})
};
$scope.getEnv = function(host) {
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
};
$scope.loadDict();
$scope.getEnv("host1");
}
I can manually call each of these methods and get the results I want from the console, using the MY_SCOPE variable. If I hard-code the dictionary, it works. If I console.log $scope.dict from anywhere in the code except from inside of the $scope.getEnv function, I get the result I expect. As soon as $scope.getEnv is involved, $scope.dict = {}.
I've tried hard-coding the keys into the dictionary. I've tried moving the definition around in the code. I've tried exporting the loadDict method into a factory. All to no avail. Ideas?
The $http.get call in $scope.loadDict is asynchronous. getEnv is getting called before your dictionary has been loaded. You need to call getEnv once that data has come back.
Have loadDict return the $http.getcall which will give you a promise. You can then chain on to that promise a success callback.
You should also put your $http calls in some sort of service to do it the 'angular' way :)
Try this instead:
$scope.loadDict = function() {
return $http.get('api/call/').
success(function(data){
for (env in data.environment) {
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
}).error(function(data){
console.error(data);
})
};
$scope.loadDict().then(function(result){
$scope.getEnv("host1");
}
Your problem is that you didn't deal with the fact that loadDict is async internally.
One way to solve this is to wait for it to complete by returning a promise from it and waiting for that promise to be resolved.
There are other ways to go about this, but this is probably one of the ways that is closest to what you already have:
// inject $q so you can make a promise
function AppController($scope, $http, $q) {
window.MY_SCOPE = $scope;
$scope.env = "Local";
$scope.dict = {};
$scope.loadDict = function() {
// set up the deferred response
var deferred = $q.defer();
$http.get('api/call/').
success(function(data){
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
// all is well so resolve the promise
deferred.resolve();
}).error(function(data){
console.error(data);
// reject the promise
deferred.reject(data);
})
return deferred.promise;
};
$scope.getEnv = function(host) {
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
};
$scope.loadDict().then(
function () {
$scope.getEnv("host1");
},
function (err) {
// whatever you want to do if the loadDict function failed to do its job
}
);
}
$scope.getEnv() is being called before $http.get() has returned data. You need to call $scope.getEnv() within the $http.get().success() block, like so:
$scope.loadDict = function() {
$http.get('api/call/').success(function (data) {
for (env in data.environment) {
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
$scope.getEnv("host1");
}).error(function(data){
console.error(data);
});
};
You need to treat things asynchronously . success is an asynchronous callback while getEnv is synchronous.
The solution in this case is to define a promise in loadDict and resolve it on success call.
Then , in the controller getEnv method you would write code after promise is resolved:
Roughly the code will be like this, I have not tested it, just wrote to give you idea:
$scope.loadDict = function() {
var deferred = $q.defer(); // to define a promise
$http.get('api/call/').
success(function(data){
deferred.resolve(data);//resolve the promise on success
}
}).error(function(data){
console.error(data);
})
return deferred.promise;//return promise
};
$scope.getEnv = function(host) {
$scope.loadDict().then(
function(data) {
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
});
};
I am unable to wrap my brain around the concept of asynchronous requests.
I have a controller for my view, which is creating an object instance from a provider:
va.controller('VaCtrl',function($scope,$shipment){
$scope.shipment = $shipment.Shipment();
});
The provider:
Shipment.provider('$shipment',function(){
this.$get = function($http){
function Shipment(){
}
Shipment.prototype.fetchShipment = function(){
var shipment = undefined;
$http.post('../sys/core/fetchShipment.php',{
// some data to POST
}).then(function(promise){
shipment = promise.data;
});
return shipment;
};
return {
Shipment: function(){
return new Shipment();
}
}
}
});
My goal is to get access to the data from Shipment.prototype.fetchShipment() inside my controller. My approach:
$scope.fetchShipment = function(){
var shipment = $scope.shipment.fetchShipment();
console.log(shipment); // undefined
};
However, this will return undefined.
I read about $q, and defers, promises and callbacks, and now i am like WTF; all i want to do is to push the retrieved data to my controller, what is the best possible way to do so?
You should modify your code as shown below to return the promise from fetchshipment directly, and then use then() inside your controller.
Shipment.prototype.fetchShipment = function(){
return $http.post('../sys/core/fetchShipment.php',{
// some data to POST
})
};
$scope.fetchShipment = function(){
var shipment = $scope.shipment.fetchShipment().then(function(data){;
console.log(data);
});
};
Explanation to Code :
Calling $http return a promise which is resolved when you get the data from the server. In the code above, I have returned $http.post from service function which returns a promise. So in the controller you are waiting for promise to be resolved, and when the promise is resolved, the result is logged to the console.
Read about more promise documentation on angular:
http://docs.angularjs.org/api/ng.$q
http://docs.angularjs.org/api/ng.$http
Just the give you an example how to get your example working with your own promise.
It's much more simple if you use $http builtin promise, so it's an $q-example:
angular.module('myApp', []).controller("myAppCtrl", function ($scope, $shipment) {
$shipment.Shipment().fetchShipment().then(function (shipment) {
$scope.shipment = shipment
});
}).provider('$shipment', function () {
this.$get = function ($http, $q) {
function Shipment() {
}
Shipment.prototype.fetchShipment = function () {
var defered = $q.defer();
demodata = {name: "jan", id:8282};
$http.post('/echo/json/', 'json=' + encodeURIComponent(angular.toJson(demodata)), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(function (response) {
//resolve promise
defered.resolve(response.data);
});
return defered.promise;
};
return {
Shipment: function () {
return new Shipment();
}
}
}
});
<div ng-controller="myAppCtrl">{{shipment}}</div>
JSFiddle (use JSFiddle echo-service as data provider):
http://jsfiddle.net/alfrescian/ayke2/
More about promises:
http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/
http://www.egghead.io/video/o84ryzNp36Q
AngularJS : Where to use promises?
stackoverflow.com/questions/15604196/… egghead.io/video/o84ryzNp36Q
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