Angular service testing promises - angularjs

I found myself in a little callback hell and found it difficult to test.
I hope maybe some of you guys could help me to figure this out
I have 2 services and my problem is with the fooService.getModel function
app.service('fooService', function($q, barService){
return {
getModel: function(){
var deferred = $q.defer();
barService.get().then(function(response){
//my bareService mock allow me to reach up to this point
var result = {};
//some processing logic (if, else, etc)
console.log('resolve result') // I totally see this log
deferred.resolve(result); //this is what i want to test
});
return deferred.promise;
},
process: function(){
this.getModel().then(function(result){
if(result.success){
barService.post().then(function(){
//whatever i dont care
})
}
});
}
}
});
and barService which contain 2 methods(get and store) basically $http calls.
for bar service i dont have problems testing because logic is isolated
I'm just faking the $httpBackend calls, with different responses.
the problem that i found when i want to verify the result object being returned on getModel function.
my test is something like
it('should test the returning model', inject(function(fooService){
barService.get.and.returnValue({then: function(successCallback){
successCallback({status:204, data: {}});
}});
var result;
fooService.getModel().then(function(response){
console.log('unit test then') // never gets in here
result = response;
});
//or anything related to the result object
expect(result).toEqual(mockedModel);
expect(result.success).toBeTruthy()
})
what i have seen is that if my getModel function will execute the http call itself, i can evaluate the result in my unit test.
something like
getModel: function () {
var deferred = $q.defer();
$http.get(url).then(function (response) {
deferred.resolve(response);
})
.catch(function (response) {
deferred.reject(response);
});
return deferred.promise;
}
i would definetly love to extract the functionality of my barService to my fooService, but this is very likely to be rehusable that why i extracted to a separate service.
do you have any idea how can i fix my unit test to evaluate the result response?
i hope its clear
thanks for the help guys

nevermind found that i even it is a service, i need to do $scope.digest in my unit test.
thanks!

Related

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);
}
);
});

AngularJS how do I execute code only after a promise is resolved? (with Restangular)

This might be a nooby question but I still haven't been able to get my head around promises and specifically how to write code with them. (I've read several articles but most of them are abstract and I simply haven't written enough to have a clear picture)
I've got an AngujlarJS application that gets data through a http request to another server which sends a promise at first. I've been able to retrieve the response from the promise and use it in my app. However because my code is poorly written. It executes other code before the promise is resolved leading to problems. It starts loading the page before it has the data.
what i have is:
var userTotals = *http request which returns a promise
$scope.data = userTotals.$object
//code that does someting with $scope.data
What i need is (I think)
var userTotals = *http request which returns a promise
$scope.data = userTotals.$object.
beforethisresolves(function{
show fancy loading icon or something })
.whenthis resolves(function{
//code that does someting with $scope.data
}
however I can't get the syntax correct.
This is what it looks like in general:
var promise = $http.post('/url');
console.log('Request started');
promise.then(function(result) {
console.log('Success');
console.log(result);
}, function() {
console.log('Failure');
});
In fact, $q AngularJS documentation helped me a good deal to get my head around promises concept.
Hope this helps!
var successCallback = function(){//...};
var errorCallback = function(){//...};
$http
.post('url', data)
.success(successCallback)
.error(errorCallback);
//OR
$http
.post('url', data)
.then(successCallback, errorCallback);
Assuming that you're using Bootstrap modal you can do the following:
function openModalWithProgressIndicator(deferred) {
const progressModal = $uibModal.open({
templateUrl: 'templates/modals/progress.html',
backdrop: 'static'
});
return deferred.then(function (value) {
return value;
}).finally(function () {
progressModal.close();
});
}
The deferred argument passed to this function is a promise. That said you can now do the following:
const deferred = $http.post('http://somewhere/data', {foo: 'bar'});
openModalWithProgressIndicator(deferred)
.then(function (httpResponse) {
// do sth with httpResponse.data
}).catch(function (error) {
// do sth with error
});
So the main point to note is the finally callback that's always executed.

q.all not working for multiple promises

I have the following q.all calling to resolve two promises. I checked all the posts and tried all other ways of implementation q.all and its the same case
var xyzdeffered = $q.defer();
service1.getServiceDetail1($routeParams.id).then(function(promise) {
xyzdeffered.resolve(promise);
});
var abcdeffered = $q.defer();
service2.getServiceDetail2($routeParams.id).then(function(promise) {
abcdeffered.resolve(promise);
});
$q.all([ xyzdeffered, abcdeffered ]).then(function(data) {
$scope.variable = data;
});
I am expecting the variable in q.all should get populated only after the first two promises are resolved. But unfortunately the service call itself doesn't get returned with data and the control moves on to the q.all. I find this weird because as per documentation the q.all is called only when your promises are returned with 200 response and are resolved.
I checked analysing the network calls and also put some alert to see the sequence of the code and find that the q.all alert is the first alert to be popped up and then the other promises are resolved.
Its really making me mad as why a simple implementation of q.all doesnt work..
Any help will be appreciated.
Why not directly call $q.all on first promise ?
$q.all([
service1.getServiceDetail1($routeParams.id),
service2.getServiceDetail2($routeParams.id)
]).then(function(data) {
//Array of result [resultOfgetServiceDetails1, resultOfgetServiceDetails2]
$scope.variable = data;
});
You need to reference the promise on the $q.defer() return object:
$q.all([ xyzdeffered.promise, abcdeffered.promise ])
For example you have you run multiple sq-lite queries synchronously just pass the array of queries(you can pass anything) as args into the call of this method.
function methodThatChainsPromises(args,tx) {
var deferred = $q.defer();
var chain = args.map(function(arg) {
var innerDeferred = $q.defer();
tx.executeSql(arg,[], function(){
console.log("Success Query");
innerDeferred.resolve(true);
}, function(){
console.log("Error Query");
innerDeferred.reject();
});
return innerDeferred.promise;
});
$q.all(chain).then(
function(results) {
deferred.resolve(true)
console.log("deffered resollve"+JSON.stringify(results));
}, function(errors) {
deferred.reject(errors);
console.log("deffered rejected");
});
return deferred.promise;
}

want multiple, independent requests resolved concurrently before a final, dependent action (AngularJS)

Promises are one of those things that I can understand while I'm looking at them, but then the comprehension vanishes when I look away.
I need to grab two pieces of data asynchronously, then combine the result and store it in a cookie. I think I could implement a vanilla promise chain without much difficulty. That is,
loadData1().then(loadData2).then(setCookie);
However, I don't need to wait for one request to finish before making the other. How can I do something like
(loadData1(); loadData2();).then(setCookie);
Here's a quick example using $q.all:
$q.all([
(function () {
var d = $q.defer();
API.get({}, function (data) {
d.resolve(data);
});
return d.promise;
})(),
(function () {
var d = $q.defer();
API.getMore({}, function (data) {
d.resolve(data);
});
return d.promise;
})()
]).then(function(responses) {
//responses contains an array of all your data responses, in the order they were chained to $q
});

How to wait till the response comes from the $http request, in angularjs?

I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.

Resources