AngularJS : chaining http promises $q in a service - angularjs

i have problems when it comes to $http promises in angularjs. i am doing this in my service: (the getSomething function should chain two promises)
the second function uses a external callback function!
app.service('blubb', function($http, $q) {
var self = this;
this.getSomething = function(uri, data) {
return self.getData(uri).then(function(data2) {
return self.compactData(uri, data2);
});
};
this.getData = function(uri) {
var deferred = $q.defer();
$http.get(uri).success(function(data) {
deferred.resolve(data);
}).error(function() {
deferred.reject();
});
return deferred.promise;
};
this.compactData = function(uri, data) {
var deferred = $q.defer();
/* callback function */
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
console.log(err);
deferred.reject(err);
}
/* end of function */
return deferred.promise;
};
});
when i use the service in my controller it doesn't output the console.log:
blubb.getSomething(uri, input).then(function(data) {
console.log(data)
});
edit:
if i define the callback function by myself in 'compactData' it works, but i am using "jsonld.compact" from https://raw.github.com/digitalbazaar/jsonld.js/master/js/jsonld.js and THIS doesn't work!
jsonld.compact(input, context, function(err, compacted) {
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
deferred.reject('JSON-LD compacting');
}
});
i am getting the console.log output in jsonld.compact but the resolve doesn't work and i don't know why..
it only works with $rootScope.$apply(deferred.resolve(compacted));

I'm using chaining promises like this:
$http.get('urlToGo')
.then(function(result1) {
console.log(result1.data);
return $http.get('urlToGo');
}).then(function(result2) {
console.log(result2.data);
return $http.get('urlToGo');
}).then(function(result3) {
console.log(result3.data);
});

Chaining promises works here : jsfiddle
In your implementation, if $http.get or compactData goes wrong your console.log(data) will not be call.
You should maybe catch errors :
blubb.getSomething(uri, input).then(function(data) {
console.log(data);
}, function(err) {
console.log("err: " + err);
});

Whenever you use an external (external to AngularJS) callback that runs in a new turn/tick, you have to call $apply() on the appropriate scope after it has been invoked. This lets AngularJS know it has to update. You'll probably want to make sure you're only calling it once -- after all of the promises have been resolved. As an aside, jsonld.js provides a promises/future API, so if you're already using promises, you don't have to do that wrapper code above. Instead you can do:
var promisesApi = jsonld.promises();
var promise = promisesApi.compact(input, context);
// do something with the promise

I would suggest you to use a Factory instead of a service.
Just return the function from the factory and use it in your controller

Related

How to get .notify() in chaining promises from $q.all?

I am using chained promises in angular js with $q service and it's working fine except the information of progressCallback ? let me draw what I have done so far?
calling function from my controller in below chainable promise way
fun1()
.then(resp1){
return fun2(resp1.id);
})
.then(resp2){
return $q.all([fun3(resp2.id),fun4(resp2.name)]);
})
.then(function(resp34){
return fun5();
})
.then(success)
.catch(errorhandler)
.finally(final);
and here is my all functions signature in service
var funX = function(param) {
var d = $q.defer();
d.notify('start with funX'); // Note: it was not working so placed inside else
doSomethingASync(param, function(err,data) {
if(err) { d.reject(err);}
else { d.notify('done with funX'); d.resolve(data); }
});
return d.promise;
});
Now my question is where do I receive this d.notify() message in my controller?
trial 1
.then(resp1, info1){
return fun2(resp1.id);
});
but it's undefined
trial 2
.then(resp1, err1, info1) {
return fun2(resp1.id);
}
but still undefined?
UPDATE
I have find a way by adding second parameter in finally()
.then().catch().finally(final, notify);
and here is my function definitions.
var errorHandler = function(err) {
console.error('Error returned from function:', err);
};
var final = function() {
console.log('Called Finally');
};
var notify = function(notification) {
console.log('Notify', notification);
};
var success = function(data) {
console.log('Success data');
console.log(data);
};
Can we get each promise function notification or this is not feasible?
But Now my query changed to
How do we add a .notify for the $q.all() ?
as I understand that $q.all returns a single promise which contains all promise resolve data;

Confusion about when Angular and when service calls need digest

I'm running into an issue that I've managed to find a fix for but I was looking for some feedback to ensure I'm doing this the right way.
I have a function in a controller that makes a call to a service. If it fails, for whatever reason, I display an error message to the user.
//Controller
vm.login = function() {
vm.error = "";
Sessions.create(vm.user)
.then(function(result) {
$state.go("home");
})
.catch(function(result) {
vm.error = result.data.errors;
});
};
//Service
Sessions.create = function(data) {
return $http.post(API + "sessions", data)
.then(function(response) {
return response.data;
});
};
//View
.alert.alert-danger(role="alert" ng-show="vm.error") {{vm.error}}
The code above works exactly as intended. When it hits vm.error = result.data.errors that error message is correctly displayed to the user.
As I attempt to add some additional functionality to that service (localforage) I'm getting some weird behavior. Here's my new code:
//Controller
vm.newLogin = function() {
vm.error = "";
Sessions.login(vm.user)
.then(function(result) {
$state.go("home");
})
.catch(function(result) {
vm.error = result.data.errors;
$scope.$digest(); //NEED THIS?!?
});
};
//Services
Sessions.create = function(data) {
return $http.post(API + "sessions", data)
.then(function(response) {
return response.data;
});
};
Sessions.login = function(data) {
return new Promise(function(resolve, reject) {
Sessions.create(data)
.then(function(result) {
//do stuff
})
.then(function(result) {
return resolve(result);
})
.catch(function(err) {
return reject(err);
});
});
};
//View
.alert.alert-danger(role="alert" ng-show="vm.error") {{vm.error}}
With this code the error message that's set in newLogin isn't displayed to the user unless I add $scope.$digest(); after it. Am I doing something wrong here? I can log and see the error message in catch inside both login and newLogin. Why is digest only needed in the second version?
Using the browser Promise API is problematic. You should use the $q service to create promises. It is better integrated with the AngularJS framework. See AngularJS $q Service API Reference.
Usually, you don't call $digest() directly in controllers or in directives. Instead, you should call $apply() (typically from within a directive), which will force a $digest(). But in your case, the AngularJS $q service will take care of calling $apply(). For more information, see AngularJS $rootScope.scope API Reference -- $digest

how to wait until promise returns when using $resource

I have a my controller with an activate method which waits for promises to return before going forward.
function activate() {
var promises = [getNodesGroup(), getNodes()];
common.activateController(promises, controllerId)
.then(function () {
log('Activated');
});
}
Both of the promises had similar approach to get the data and that was using $http in datacontext.getNodeGroups() and datacontext.getNodes() methods.one of which is like this
function getNodesGroup() {
return datacontext.getNodeGroups().then(function (response) {
$scope.nodeGroups = response.data;
//other logic
});
All was working fine until now when i tried to use $resource for one of teh promises (getNodes()). my resource for node is setup like this
(function () {
'use strict';
var nodeServiceRoot = '/api/Nodes';
var GetAllNodesUrl = nodeServiceRoot + '/GetAllNodes';
angular.module('common.service')
.factory("nodeResource",["$resource","appSettings", nodeResource])
function nodeResource($resource, appSettings) {
return $resource(appSettings.serverPath + GetAllNodesUrl);
}}());
and i am trying to consume it like this
function getNodes() {
$scope.nodes = nodeResource.query();
$scope.nodes.$promise.then(function (response) {
$scope.nodes = response;
//other logic
});
i don't know how to return getNodes() so that my activate functions wait for it before proceeding. right now whats happening is that sometime both functions are run when it hits log('Activated'); line of activate functions and some time only one promise with old $http approach (getNodeGroups()) is run but not the other.
well i guess the answer was very simple. i was missing 'return' in getNodes()
so what worked for me is
function getNodes() {
$scope.nodes = nodeResource.query();
**return** $scope.nodes.$promise.then(function (response) {
$scope.nodes = response;
//other logic
});

Angular recursive factory method

I would like to ask is it possible to return recursive factory method. I will show some code so you could understand me more.
We have factory:
angular.module('module')
.factory('synchronizationStatus', ['$http', 'apiBase', '$timeout', function ($http, apiBase, $timeout) {
var service_url = apiBase.url;
function syncCompleted ( correlationId ) {
checkSync (correlationId)
.then(function(response){
$timeout(function() {
// if I get response.data true, i want to proceed to controller
if(response.data){
return "now I want to return result to controller"
}
else {
// check again
checkSync(correlationId)
}
}, 500);
})
}
function checkSync( correlationId ){
return $http.get(service_url + query);
}
return {
syncCompleted: syncCompleted
};
}]);
Main idea with this factory method is that i constantly (every 500ms each) send ajax request to backend and check if some operation is completed or no, and when it is completed i would like to send promise to controller function, which looks like this:
function save( client ) {
clients.addClient( client )
.then( function(response) {
synchronizationStatus.syncCompleted(response.data.CorrelationId);
}, onSaveError)
.then( redirectToList, onSaveError )
.finally( unblock );
}
after backend returns true to my factory method i would like to execute other functions in my controller. Of course I could do recursion in my controller and it would solve this problem. Although I have to reuse this recursion in many other controllers so I would like to reuse this method.
Yes, you should be able to do this, but you need to change the code in your factory a little:
angular.module('module')
.factory('synchronizationStatus', [
'$http',
'apiBase',
'$timeout',
function ($http, apiBase, $timeout) {
var service_url = apiBase.url;
function waitSyncCompletion( correlationId ) {
return checkSync (correlationId)
.then(function(response){
if (response.data) {
return "all done!";
}
return $timeout(function() {
// check again
return waitSyncCompletion(correlationId);
}, 500);
});
}
function checkSync( correlationId ){
var query = '...'; // determine query
return $http.get(service_url + query);
}
return {
waitSyncCompletion: waitSyncCompletion
};
}
]);
Then in your controller, you would need to use a return so that you can wait for the operation to complete:
function save( client ) {
clients.addClient( client )
.then( function(response) {
return synchronizationStatus.waitSyncCompletion(response.data.CorrelationId);
})
.then(function (result) { console.log(result); })
.then( redirectToList )
.catch( onSaveError )
.finally( unblock );
}
Make use of a custom deferred (promise), using Angular's $q. Make sure that you inject the $q dependency in your factory and create the deferred at the start of your factory:
var deferred = $q.defer()
At the point where the promise must resolve, add:
deferred.resolve();
And make sure you return the promise at the bottom of your syncCompleted function:
return deferred.promise;
Additionally, you could add deferred.reject() if you also want error-handling. Furthermore, you could add arguments to reject() and resolve() if you need to.
As an alternative, you could achieve the same goal without using $q. $timeout() also returns a promise, and if you return a promise in the callback given to then(), it will kind of 'replace' the 'parent' promise.

How to mock an angular $http call and return a promise object that behaves like $http

Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.
For example, I have a service that is similar to this:
angular
.module('myservice')
.factory('MyService', ['$http', function($http) {
return {
get : function(itemId) {
if (isInTestingMode) {
// return a promise obj that returns success and fake data
}
return $http.get("/myapp/items/" + itemId);
}
};
} ]);
And in my controller, I have a call to the aforementioned service that looks similar to this:
// Somewhere in my controller
MyService.get($scope.itemId)
.success(function(data) {
$scope.item = data;
})
.error(function(data, status, headers, config) {
$scope.notFound = true;
});
I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode".
Is it possible to fake an HttpPromise in the way that I described in the service?
Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?
return {
get : function(itemId) {
if (isInTestingMode) {
var promise = $.defer().promise;
// Mimicking $http.get's success
promise.success = function(fn) {
promise.then(function() {
fn({ itemId : "123", name : "ItemName"}, 200, {}, {});
});
return promise;
};
// Mimicking $http.get's error
promise.error = function(fn) {
promise.then(null, function(response) {
fn("Error", 404, {}, {});
});
return promise;
};
return promise;
}
return $http.get("/myapp/items/" + itemId);
}
}
Just use the deferred method of the $qservice
var fakeHttpCall = function(isSuccessful) {
var deferred = $q.defer()
if (isSuccessful === true) {
deferred.resolve("Successfully resolved the fake $http call")
}
else {
deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
}
return deferred.promise
}
And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).
fakeHttpCall(true).then(
function (data) {
// success callback
console.log(data)
},
function (err) {
// error callback
console.log(err)
})
I found that this post is similar to what I was asking.
However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:
angular
.module('myApp', ['ngMockE2E'])
.run(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(/^partials\/.+/)
.passThrough();
$httpBackend
.whenGET(/^\/myapp\/items\/.+/)
.respond({itemId : "123", name : "ItemName"});
}]);
See this documentation for more information on $httpBackend.
I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.
Here is how it works:
beforeEach(inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
mockSvc = {
someFn: function () {
},
someHttpFn: function () {
}
};
// use jasmin to fake $http promise response
spyOn(mockSvc, 'someHttpFn').and.callFake(function () {
return {
success: function (callback) {
callback({
// some fake response
});
},
then: function(callback) {
callback({
// some fake response, you probably would want that to be
// the same as for success
});
},
error: function(callback){
callback({
// some fake response
});
}
}
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
MyActualSvc: mockSvc
});
}));
You can implement your FakeHttp class:
var FakeHttp = function (promise) {
this.promise = promise;
this.onSuccess = function(){};
this.onError = function(){};
this.premise.then(this.onSuccess, this.onError);
};
FakeHttp.prototype.success = function (callback) {
this.onSuccess = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSucess, this.onError);
return this;
};
FakeHttp.prototype.error = function (callback) {
this.onError = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSuccess, this.onError);
return this;
};
Then in your code, you would return a new fakeHttp out of the promise.
if(testingMode){
return new FakeHttp(promise);
};
The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.
easy peasy!
You can do it using angular-mocks-async like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);

Resources