How to asynchronously call functions - angularjs

I have some list of functions to call in controller say
$scope.trader = {};
$scope.getUserLocation();
$scope.searchPaymentMethod($scope.trader.cityname);
$scope.getUserCurrency();
and each of these functions make an HTTP call to get some data. Say getUserLocation does this...
$scope.getUserLocation = function() {
var dataPromise = locationServiceCustomised.getCurrentLocation();
dataPromise.then(function(result) {
$scope.trader.cityname=result.countryName;
});
}
and these all functions set some value to $scope.trader whose value is being used in calling another function.
Now how to make one by one Asynchronous call to each of these functions so that these functions work one after another.
My full code is somewhat like this...
$scope.trader = {};
$scope.getOfferList = function() {
if($scope.trader.cityname == null || $scope.trader.cityname == '') {
$scope.getUserLocation();
}
$scope.searchPaymentMethod($scope.trader.cityname);
$scope.getUserCurrency();
}
$scope.getUserLocation = function() {
var dataPromise = locationServiceCustomised.getCurrentLocation();
dataPromise.then(function(result) {
$scope.trader.cityname=result.countryName;
});
}
$scope.searchPaymentMethod = function(country) {
locationService.paymentMethod({cityname: country}, function(data) {
------- Some Functionality-------
});
};
$scope.getOfferList();

If you have asynchronous functions that depend on values obtained from previous asynchronous functions, you need to chain promises with .then. If the functions can be called in parallel, then you need to use $q.all.
In your case, searchPaymentMethod is strictly dependent on value from getLocation, while getUserCurrency can be done in parallel.
$scope.getOfferList = function() {
var paymentMethodPromise =
$q.when($scope.trader.cityname)
.then(function(cachedLocation){
if (cachedLocation) return cachedLocation;
return getUserLocation();
})
.then(function(location){
// cache, if needed, but better do this in a service
$scope.trader.cityname = location;
return searchPaymentMethod(location);
});
var currencyPromise = getUserCurrency();
// when all complete, calculate offerList
return $q.all({
paymentMethod: paymentMethodPromise,
currency: currencyPromise })
.then(function(data){
var paymentMethod = data.paymentMethod,
currency = data.currency;
// based on that get offerList
return offersList;
})
};
Needless to say, that for this to work, the expectation is for all these functions (i.e. searchPaymentMethod, getUserLocation, etc...) return a promise of the right value. As an example, getUserLocation doesn't do that now, and should be changed to something like the following:
function getUserLocation(){
return locationServiceCustomised
.getCurrentLocation()
.then(function(location){
// manipulate the result if needed
return location.cityName;
})
}
A few other things to note:
You don't need to expose every function on the $scope - expose only the ones that need to be invoked from the View, e.g. <button ng-click="getOfferList()">
It's better to leave the cached-or-not decision to the service (that should implement these functions) and leave a clean API for the controller.

You basically need to chain promises. So, first of all, all your functions have to return a promise. So, for your first function you would have:
$scope.getUserLocation = function() {
var deferred = $q.defer();
locationServiceCustomised.getCurrentLocation.then(function(result) {
deferred.resolve(result);
});
return deferred;
}
Then just chain it:
$scope.getUserLocation()
.then($scope.searchPaymentMethod)
.then($scope.getUserCurrency);
where
$scope.searchPaymentMethod = function(input) {
var deferred = $q.defer();
$scope.trader.cityname = input.cityname;
// do some computationn
deferred.resolve(whateveryouwanttoreturn);
returnn deferred;
}

Related

Calling two parent controller scope function from child controller, one after another in angular.js

I have one Angular.js method from child controller, where it makes a call to twop parent controller methods one after another. But the second function should get the account data from from the first function and then will update call data like below:
filter.filterAccountsByProductMetrics1 = function(productWithSegmentations12) {
accountService.fetchAccountForRecordType([filter.selectedHcpHco.Name.display])
.then(function(resp) {
$scope.accountDataUpdate({
accounts: resp.data
});
var productId = null;
if(filter.selectedMySetupProduct.Product_vod__c) {
productId = filter.selectedMySetupProduct.Product_vod__c.value;
}
callService.getCallsForProductId(productId)
.then(function(calls) {
filter.filterRecords[filterType.product.value] = calls;
$scope.callDataUpdate({
calls: applyAllFilterOnCalls()
});
});
});
};
I've checked both the functions are getting called but the sequence is not maintained. How to make sure the two parent functions get called one after another.
EDIT: function accountDataUpdate:
call.accountDataUpdate = function(accounts) {
call.accounts = accounts;
getCallDetails(getCallIdsFromCallsForFilteredAccount())
.then(function() {
updateProductFrequencyTableData();
updateAccountDetailData(true);
});
updateDailyFrequencyChartData();
updateWeeklyFrequencyChartData();
updateCallFrequencyTableData();
updateAccountFrequencyData();
$timeout(function() {
$scope.$broadcast('updateDoughnutChart');
$scope.$broadcast('updateBarChart');
});
};
Modify accountDataUpdate to return a promise:
call.accountDataUpdate = function(accounts) {
call.accounts = accounts;
var promise = getCallDetails(getCallIdsFromCallsForFilteredAccount())
.then(function() {
updateProductFrequencyTableData();
updateAccountDetailData(true);
updateDailyFrequencyChartData();
updateWeeklyFrequencyChartData();
updateCallFrequencyTableData();
updateAccountFrequencyData();
return $timeout(function() {
$scope.$broadcast('updateDoughnutChart');
$scope.$broadcast('updateBarChart');
});
});
return promise;
};
Then use that promise for chaining:
filter.filterAccountsByProductMetrics1 = function(productWithSegmentations12) {
return accountService.fetchAccountForRecordType([filter.selectedHcpHco.Name.display])
.then(function(resp) {
return $scope.accountDataUpdate({
accounts: resp.data
});
}).then(function() {
var productId = null;
if(filter.selectedMySetupProduct.Product_vod__c) {
productId = filter.selectedMySetupProduct.Product_vod__c.value;
}
return callService.getCallsForProductId(productId)
}).then(function(calls) {
filter.filterRecords[filterType.product.value] = calls;
return $scope.callDataUpdate({
calls: applyAllFilterOnCalls()
});
});
};
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
For more information, see AngularJS $q Service API Reference - Chaining promises.

How do I make a promise resolve as an object in the view?

I'm trying to wrap a third party library to return an object that resolves into an object that can be displayed in the view, similar to how $resource() works. I'm aware that I can manually do .then() on the promise and then set the value, but I wanted the result to seamlessly return similar to how I can do:
this.Value = $resource("/someresource").get();
How would I change the below SomeThirdPartyFunction() to return an object that resolves in the view.
Here's an example of what I'm trying to do:
angular.module('testApp', []).controller('TestController', function ($timeout, $q) {
var TestController = this;
var SomeThirdPartyFunction = function () {
var Deferred = $q.defer();
var Promise = Deferred.promise;
$timeout(function () {
Deferred.resolve("abcd");
}, 3000);
return Promise;
};
TestController.Value = SomeThirdPartyFunction();
/* I don't want to do this:
SomeThirdPartyFunction().then(function(Value) {
TestController.Value = Value;
});*/
});
And here's a plunker: https://plnkr.co/edit/HypQMkaqXmFZkvYZXFXf?p=preview
Every example I've seen using promises just wraps $http calls, but I haven't seen any examples of calling third party libraries that return promises that resolve into objects.
From the AngularJS document:
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.
So instead of return a promise, you can do something like this:
var SomeThirdPartyFunction = function() {
var getAComplextObject = function() {
return {
number: 42,
method: function() {
return this.number + 1;
}
};
};
var returnValue = {};
$timeout(function() {
console.log("Resolved!");
Object.assign(returnValue, getAComplextObject());
}, 1000);
return returnValue;
};
You can wrap it in a promise and make the promise part of the return value, doing that you can make it thenable (aka a Promise)
Under-the-hood, $resource uses angular.copy:
function myResourceGet(params) {
var emptyObj = {};
emptyObj.$resolved = false;
emptyObj.$promise = $http.get(url, {params:params})
.then(function(response) {
angular.copy(response.data, emptyObj);
}).finally(function() {
emptyObj.$resolved = true;
});
return emptyObj;
}
From the Docs:
angular.copy Overview
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
The $resource service only works when the data is an object or an array. It does not work with primitives such as a string or number.

Find and return item in Angular deferred promise

So i have a bit of a confusion with angular promises.
I have a service, storing a list of users
.service('user', ['$q','$http', function ($q, $http) {
var services = {};
var _def_userLsit = $q.defer();
$http.get('/api/acc/getlist').then(function (data) {
_def_userLsit.resolve(data);
})
services.getUserListQ = function () {
return _def_userLsit.promise;
}
return services;
}])
after injecting it, i can load my list like this in a controller:
user.getUserListQ().then(function (promise) {
$scope.userHelper.userList = promise.data;
});
no problem here, got the json in the $scope, then the watchers do their jobs just fine.
Here is the Json format simplified for the question:
obj 1 { id=4, userName="foohuman", $$hashKey="object:14"}
obj 2 { id=444, userName="barhuman", $$hashKey="object:22"}
But i also want a user's name by id, and i need to run that several times ( depend on the post count ).
So my question here is, how can i return a user's name like a function call, from that promised list.
like a normal function would do, like this:
$scope.getUserById = function( id ){
return "foo";
//some magic needed here
}
If i just iterate trough the userHelper.userList, it could be empty if it runs too early, so i need to load that trough a promise, but loading it, iterating trough, then adding a string to the $scope is not the best options, since it can run multiple times, so it can overwrite each other, pretty unpredictably if i store it in a single variable.
So any idea how can i return a nice simple string by the id, and not a promise?
EDIT: Ok, so i can't really return a nice string, because it have to be some kind of callback, but i can process the data from the promise, so i ended up loading user data into a array like this:
user.getUserListQ().then(function (promise) {
var uArr = [];
angular.forEach(promise.data, function ( value, key ) {
uArr[value.id] = value.userName;
})
$scope.userHelper.uArr = uArr;
});
and in the html I can boldly write {{userHelper.uArr[taskData.tOwner]}}.
First of all, you're making promises harder than they should be by using the explicit construction antipattern. Your first code snippet can be replaced with this:
.service('user', ['$q','$http', function ($q, $http) {
var services = {};
var pUserList = $http.get('/api/acc/getlist');
services.getUserListQ = function () {
return pUserList;
};
return services;
}]);
Regarding your question:
So any idea how can i return a nice simple string by the id, and not a promise?
This is the wrong mindset to have. For getting a user by their ID, you need to think in terms of promises. You don't know exactly when the data is going to be ready, so this means that your getUserId method should return a promise. You can write it like this:
services.getUserById = function (id) {
return pUserList.then(function (users) {
return users.filter(function (user) { return user.id === id; })[0];
});
};
in your controller, you can use it like this:
myService.getUserById(4).then(function (user) {
$scope.myUser = user;
});

Add callback to angularjs service method

Is there any way of adding a callback method to a function that calls service
in a function i am calling multiple service methods and i want to a set a callback method to that function.
I tried this but no luck
$scope.MyFunction() = function()
{
//Multiple Service calls
$scope.callService1 = service.CallService1() //returns true on success
$scope.callService2 = service.CallService2() //returns true on success
$scope.callService3= service.CallService3() //returns true on success
if($scope.callService1 && $scope.callService2 && $scope.callService3)
{
$scope.CallbackMethod ();
}
}
$scope.CallbackMethod = function()
{
alert('CallbackMethod')
}
I tried this one too but its not in sync as the service calls takes some time.
$scope.MyFunction() = function(CallbackMethod)
{
//Refer Above Code
}
CallService service Method is something simple like
$http.post('/InstStrategy/ReadAll').then(function (response) {
return true
});
Assuming that CallService1, CallService2 and CallService3 are async methods, they should return a deferred promise. For example:
this.CallService1 = function() {
// Once the result is available resolve the promise.
return $http.post('/InstStrategy/ReadAll').then(function(response) {
return true;
});
}
Now, it's time to define your function:
$scope.MyFunction = function(callback) {
var callService1 = service.CallService1(),
callService2 = service.CallService2(),
callService3 = service.CallService3();
// We want to wait for all these three methods to complete.
$q.all([callService1, callService2, callService3])
.then(function(results)) {
// Results is an array containing the results of each of your service calls.
var allTrue = true;
angular.forEach(results, function(result) {
if (!result) allTrue = false;
});
// If all the service calls where true, perform our callback.
if (allTrue) callback();
});
};
Note that the allTrue check is not really necessary, since the promises are systematically resolved with true.

AngularJS : How to test promise logic with a resolve in it?

I have a service method that has some caching logic:
model.fetchMonkeyHamById = function(id)
{
var that = this;
var deferred = $q.defer();
if( that.data.monkeyHam )
{
deferred.resolve( that.data.monkeyHam );
return deferred.promise;
} else {
return this.service.getById( id).then( function(result){
that.data.monkeyHam = result.data;
});
}
};
I know how to use $httpBackend to force the mocked data to be returned. Now, how do I force it to resolve (and then test the result) when I've set the data explicitly?
I want to test the result in the controller then() function:
MonkeyHamModel.fetchMonkeyHamById(this.monkeyHamId).then( function() {
$scope.currentMonkeyHam = MonkeyHamModel.data.monkeyHam;
});
Then my test I want to explicitly set the data (so it loads from memory "cache" instead of httpBackend)
MonkeyHamModel.data.monkeyHam = {id:'12345'};
MonkeyHamModel.fetchMonkeyHamById( '12345');
// How to "flush" the defer right here like I would have flushed httpBackend?
expect( $scope.currentMonkeyHam.id ).toEqual('12345'); //fails because it is not defined yet since the $q never resolved
Where $scope is just the scope of my controller, but called $scope here for brevity.
UPDATE:
The suggested answer does not work. I need the function to return a promise, not a value that is the result of a promise:
model._monkeyHams = {} // our cache
model.fetchMonkeyHamById = function(id){
return model.monkeyHams[id] || // get from cache or
(model.monkeyHams[id] = this.service.getById(id).then(function(result){
return result.data;
}));
};
The following requires that you have touched the server already. I create a model on the front end (currentMonkeyHam) or whatever, and don't load it back after the first POST (an unnecessary GET request). I just use the current model. So this does not work, it requires going out to the server at least once. Therefore, you can see why I created my own deferred. I want to use current model data OR get it from the server if we don't have it. I need both avenues to return a promise.
var cache = null;
function cachedRequest(){
return cache || (cache = actualRequest())
}
Your code has the deferred anti pattern which makes it complicated - especially since you're implicitly suppressing errors with it. Moreover it is problematic for caching logic since you can end up making multiple requests if several requests are made before a response is received.
You're overthinkig it - just cache the promise:
model._monkeyHams = {} // our cache
model.fetchMonkeyHamById = function(id){
return model.monkeyHams[id] || // get from cache or
(model.monkeyHams[id] = this.service.getById(id).then(function(result){
return result.data;
}));
};
In your case, you were caching all IDs as the same thing, the general pattern for caching promises is something like:
var cache = null;
function cachedRequest(){
return cache || (cache = actualRequest())
}
Creating deferred is tedious and frankly - not very fun ;)
You can use setTimeout (or $timeout) for resolving the promise.
You can modify your code as -
model.fetchMonkeyHamById = function(id)
{
var that = this;
var deferred = $q.defer();
if( that.data.monkeyHam )
{
setTimeout(function() {
deferred.resolve(that.data.monkeyHam);
}, 100);
return deferred.promise;
} else {
return this.service.getById( id).then( function(result){
that.data.monkeyHam = result.data;
});
}
};
EDIT:
Modified as per Benjamin's suggestion -
Using $rootScope.digest() - code should be something like this
MonkeyHamModel.data.monkeyHam = {id:'12345'};
MonkeyHamModel.fetchMonkeyHamById( '12345');
$rootScope.digest();
We've done something similar in our code base, but instead of having an object with state that constantly changed we went with something that looks more like a traditional repository.
someInjectedRepoistory.getMonkeyHamModel().then(x => $scope.monkeyHam = x);
Repository{
getMonkeyHamModel() {
var deferred = $q.defer();
if( this.cache.monkeyHam )
{
deferred.resolve( this.cache.monkeyHam );
} else {
return this.service.getById( id).then( function(result){
this.cache.monkeyHam = result.data;
});
}
return deferred.promise
}
}
There are no problems with returning a completed deferred. That's part of the purpose of the deferreds, it shouldn't matter when or how they are resolved, they handle all of that for you.
As for your test we do something like this.
testGetFromService(){
someInjectedRepoistory.getMonkeyHamModel().then(x => verifyMonkeyHam(x));
verifyHttpBackEndGetsCalled()
}
testGetFromCache(){
someInjectedRepoistory.getMonkeyHamModel().then(x => verifyMonkeyHam(x));
verifyHttpBackEndDoesNotGetCalled()
}

Resources