Execute $http requests in a particular order - angularjs

I have about 5 requests that are made when my view loads. This is for an edit form:
var reqA = function() {...};
var reqB = function() {...};
var reqC = function() {...};
var reqD = function() {...};
var reqE = function() {...};
Now I want reqA() and reqB() to load asynchronously and if possible return a single promise.
reqC() and reqD() should only load after reqA() and reqB() have finished and executed their promise.
reqE() should only load after reqC() and reqD().
This is what I want to do, however I have no idea how to go about it. I can load them all asynchronously or one after another by chaining promises but not in the way I want.

If your functions all use the $http provider, it is easy to accomplish. You will want to modify your functions to return the result of the $http call like this
function reqA() {
return $http.get(...);
}
Now that they are returning promises, you can easily use the $q provider to orchestrate your requests:
$q.all([reqA(), reqB()])
.then( () =>
$q.all([reqC(), reqD()])
.then(() => reqE())
);
For regular JS (non ES6):
$q.all([reqA(), reqB()])
.then(function() {
$q.all([reqC, reqD()])
.then(function() {
reqE();
});
});
If you don't want to return the result of $http in your functions, you will need to set them up to return a promise in one way or the other:
function reqA() {
var deferred = $q.defer();
... your request code ...
// call this if everything is ok
deferred.resolve();
// call this if there was an error
deferred.reject();
return deferred.promise;
}

Here is an example that you can take a look at that might make it clearer. It makes use of Promise which is globally available in almost all javascript environments (excluding IE and older node versions).
angular
.module('example', [])
.run(function($q) {
function requestOne() {
return $q.when("one")
}
function requestTwo() {
return $q.when("two")
}
function requestThree() {
return $q.when("three")
}
function requestFour() {
return $q.when("four")
}
function requestFive() {
return $q.when("five")
}
$q
.all([requestOne(), requestTwo()])
.then(function(responses){
console.log(responses[0] === "one");
console.log(responses[1] === "two");
return $q.all([requestThree(), requestFour()]);
})
.then(function(nextResponses){
console.log(nextResponses[0] === "three");
console.log(nextResponses[1] === "four")
return requestFive();
})
.then(function(lastResponse){
console.log(lastResponse === "five")
})
});
angular.element(document).ready(function() {
angular.bootstrap(document, [ 'example' ]);
});
I assume that you are using angular 1, if you aren't there Promise is available in the global scope in almost all environments.

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.

AngularJS +how to wait for task to finish before running next task

I have still problems with my database, but I found out, that the problems come from the fact, that opening the database takes some time, but the app is not waiting till this task has finished.
Is there a way to make angular wait till the database is opened correctly before it starts the next task?
Thanks,
Christian.
Update: 2016-08-08 - 03:13 pm
Thanks for all the answers. As I can see, my first idea with promises ($q) was right, but:
My app has an app.js, which is my main file. It only calls the InitDB. This should open the database. Then it should call the CreateTables, this creates the table, if it doensn't exist.
The rest of my app is splitted in 4 pages (templates). Every page has it's own controller.
So the idea was to init the db, to create the table, and then work with the database, but used over different controllers.
This won't work, because I would always need to put all of my stuff in the .then() of my initDB in the app.js???
This is my first AngularJS app, maybe this is the reason why I do a lot of mistakes...
Thanks,
Christian.
One of the core concepts of Angular is working with services/factories. There is ample documentation and blogs about how these work and how to use them, but the basic idea is that these are singleton "controllers" that handle shared data and methods across your entire application. Used in combination with promises, you can easily create a service that will manage communications with your database.
angular
.module('myApp')
.service('DBService', DBService)
.controller('Ctrl1', Ctrl1)
.controller('Ctrl2', Ctrl2)
.controller('Ctrl3', Ctrl3)
.controller('Ctrl4', Ctrl4);
DBService.$inject = ['$q'];
function DBService($q) {
var DBService = this;
var DBServiceDeferred = $q.defer();
DBService.ready = DBServiceDeferred.promise;
// a service is only initialized once, so this will only ever be run once
(function() {
init();
})();
function init() {
// you can use promise chaining to control order of events
// the chain will halt if any function is rejected
initDB()
.then(createTablesUnlessExist)
.then(setDbReady);
}
function initDB() {
var deferred = $q.defer();
// simulate async db initialization
$timeout(function() {
deferred.resolve();
// or reject if there is an error
// deferred.reject();
}, 5000);
return deferred.promise;
};
function createTablesUnlessExist() {
//create tables if needed (only happens once)
var deferred = $q.defer();
// simulate async table creation
$timeout(function() {
deferred.resolve();
// or reject if there is an error
// deferred.reject();
}, 5000);
return deferred.promise;
}
function setDbReady() {
DBServiceDeferred.resolve();
}
}
Now you have your DB setup and you don't have to worry about it anymore. You can access the DB from any controller using the service. None of the queries will run until the DB has been initialized and the tables have been created.
Ctrl1.$inject = ['DBService', '$q'];
function Ctrl1(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Ctrl2.$inject = ['DBService', '$q'];
function Ctrl2(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Ctrl3.$inject = ['DBService', '$q'];
function Ctrl3(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Ctrl4.$inject = ['DBService', '$q'];
function Ctrl4(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Angular provides a service $q. A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing. Please refer the documentation https://docs.angularjs.org/api/ng/service/$q for the same.
$q basically revolves around the concepts of promises. Promises in AngularJS are provided by the built-in $q service.
Write a method that check if connection is established or not...which returns true or false.
app.controller('MainCtrl', function($scope, httpq) {
http.get('server call method')
.then(function(data) {
if(data.conn==true)
// do what u want
//write other calls
})
.catch(function(data, status) {
console.error('error', response.status, response.data);
})
});
you can use $q library
example:
app.service("githubService", function ($http, $q) {
var deferred = $q.defer();
this.getAccount = function () {
return $http.get('https://api.github.com/users/haroldrv')
.then(function (response) {
// promise is fulfilled
deferred.resolve(response.data);
// promise is returned
return deferred.promise;
}, function (response) {
// the following line rejects the promise
deferred.reject(response);
// promise is returned
return deferred.promise;
})
;
};
});
using above service:
app.controller("promiseController", function ($scope, $q, githubService) {
githubService.getAccount()
.then(
function (result) {
// promise was fullfilled (regardless of outcome)
// checks for information will be peformed here
$scope.account = result;
},
function (error) {
// handle errors here
console.log(error.statusText);
}
);
});

Mocking multiple return values for a function that returns a success/error style promise?

NB: Code reproduced from memory.
I have a method generated by djangoAngular that has this signature in my service:
angular.module('myModule')
.service('PythonDataService',['djangoRMI',function(djangoRMI){
return {getData:getData};
function getData(foo,bar,callback){
var in_data = {'baz':foo,'bing':bar};
djangoRMI.getPythonData(in_data)
.success(function(out_data) {
if(out_data['var1']){
callback(out_data['var1']);
}else if(out_data['var2']){
callback(out_data['var2']);
}
}).error(function(e){
console.log(e)
});
};
}])
I want to test my service in Jasmine, and so I have to mock my djangoAngular method. I want to call through and have it return multiple datum.
This is (sort of) what I have tried so far, reproduced from memory:
describe('Python Data Service',function(){
var mockDjangoRMI,
beforeEach(module('ng.django.rmi'));
beforeEach(function() {
mockDjangoRMI = {
getPythonData:jasmine.createSpy('getPythonData').and.returnValue({
success:function(fn){fn(mockData);return this.error},
error:function(fn){fn();return}
})
}
module(function($provide) {
$provide.provide('djangoRMI', mockDjangoRMI);
});
});
it('should get the data',function(){
mockData = {'var1':'Hello Stackexchange'};
var callback = jasmine.createSpy();
PythonDataService.getData(1,2,callback);
expect(callback).toHaveBeenCalled();
})
})
But when I put another it block in with a different value for mockData, only one of them is picked up.
I'm guessing that because of the order of operation something is not right with how I'm assigning mockData. How can I mock multiple datum into my djangoRMI function?

AngularJS promise and prevent redundant async calls

I have a wcf service method that gets some data and I call it using Microsoft Ajax library.
To share this data I create a dataService, and many controllers use this service.
I want every controller to get the same data after first call of getData is done, unless somebody need to refresh data and set forceRefresh to true.
My code fails because with the initialize of application 3 controler call dataService.getData and for all there start a new request. How can I make wait dataService.getData calls until the first one is finished and get same result for other subsequent ones..
angular.module('app', []).factory('dataService', function ($q) {
var data= [];
var getData= function (forceRefresh) {
console.log('getFolders called: ', reports.length);
var deferred = $q.defer();
if (forceRefresh || data.length < 1) {
WcfService.GetData(function(result) {
data= result;
deferred.resolve(data);
}, function(ex) { console.log(ex); });
} else {
deferred.resolve(reports);
}
return deferred.promise;
};
return {
getData: getData
};
});
One way would be to cache the promise, rather than the data, so it gets cached when it is created and not when the data arrives. After all, this sounds like your use case.
angular.module('app', []).factory('dataService', function ($q) {
var deferred = null;
var getData= function (forceRefresh) {
console.log('getFolders called: ', reports.length);
if(!forceRefresh && deferred) return deferred.promise;
deferred = $q.defer();
WcfService.GetData(
function(result) { deferred.resolve(data); }, // I'd promisify at a
function(ex){ deferred.reject(ex); } // higher level probably
);
return deferred.promise;
};
return {
getData: getData
};
});
how about setting a global flag in the $rootscope when a controller is querying for data, which can be checked before fetching the data by all the controllers, hence, avoiding redundant calls. The same flag can be put down when any of the controller has the promise fulfilled and data has been fetched, which can then be shared amongst all the controllers.
I found exactly what I search for
Promise queue for AngularJS services
http://inspectorit.com/tips-tricks/promise-queue-for-angularjs-services/

AngularJS running code in app.run()

I am trying to run the following code before any of my AngularJS app controllers, directives run, but unfortunately the app main page controller loads before this code finish executing, so I was wondering if there is a way to ensure that all my app controllers, directives won't run / load before this code finish completely? Thanks
myApp.run(['TokenSvc',function (TokenSvc) {
TokenSvc.getToken().then(function(serverToken){
console.log('Got it...');
}, function(status){
console.log(status);
});
}]);
Most commonly you'll see resolve in the ng-route or ui-router $state definition used for this concern, but that can be problematic. If the resolution takes a while, the user will just be staring at a blank screen. Of course, you can mitigate this problem by using an interceptor to display a loader, but I'd argue that that's outside the intended utility of interceptors.
I like to use something to manage the initialization promise(s), and inject that thing into top-level Controllers (i.e. either a Mediator or Observer pattern):
(function () {
function UserInfoLoader($q, facebookService, githubService) {
var _initPromise = null;
function initialization() {
var deferred = $q.defer(),
_initPromise = deferred.promise,
facebookLoading = facebookService.somePromiseFunc(),
githubLoading = githubService.somePromiseFunc();
$q.all([facebookLoading, githubLoading])
.then(function (results) {
// do something interesting with the results
deferred.resolve();
// set the promise back to null in case we need to call it again next time
_initPromise = null;
});
return promise;
}
this.initialize() {
// if there's already an initialization promise, return that
return _initPromise ? _initPromise : initialization();
}
}
angular.module('myApp').service('userInfoLoader', UserInfoLoader);
}());
This is great, because you can have multiple Controllers depend on the same workflow logic and they'll only produce one promise.
(function () {
function UserProfileController($scope, userInfoLoader) {
$scope.loading = true;
function load() {
userInfoLoader.initialize().then(function () {
$scope.loading = false;
});
}
load();
}
function UserMessagesController($scope, userInfoLoader) {
// same sort of loading thing
}
angular.module('myApp')
.controller('userProfileController', UserProfileController)
.controller('userMessagesController', UserMessagesController)
;
}());
To borrow from Mr. Osmani's book linked above, the loader service is like an air traffic controller. It coordinates the schedules of and passing information between multiple "airplanes", but they never have to talk to each other.
Another approach that I've seen is to use a FrontController, usually added on the body element, that manages a global loader, showing it during long-running async operations. That one's pretty simple, so I won't write it all out.
Do the fowllowing in each route:
$routeProvider.when("/your/path", {
templateUrl: "template/path",
controller: "controllerName",
resolve: {
getToken: ['TokenSvc',function (TokenSvc) {
return TokenSvc.getToken();
}]
}
});
You need that the getToken method return always the same object. Something like this:
obj.token = null;
obj.getToken = function(){
if(!obj.token){
var deferred = $q.defer();
obj.token = deferred;
deferred.promise.then(function(serverToken){
console.log("Got it. The token is ",serverToken);
}, function(status){
console.log("something is wrong ", status);
});
$http.get("url/to/token")
.success(function(data){
deferred.resolve(data);
})
.error(function(data, status){
deferred.reject(status);
});
}
return obj.token.promise;
}

Resources