I would like to create a chained promise for my service provider:
this.$get = function($q, $window, $rootScope) {
var $facebook=$q.defer();
$rootScope.$on("fb.load", function(e, FB) {
$facebook.resolve(FB);
});
$facebook.api = function () {
var args=arguments;
args[args.length++] = function(response) {
$facebook.resolve(response);
};
$facebook.promise.then(function(FB) {
FB.api.apply(FB, args);
});
return $facebook.promise;
};
return $facebook;
};
Than I call to the promise: $scope.user=$facebook.api("/me");
The problem is that because the deferred was already resolved its not wait until the api method will resolve it..
How can I chain them in a way the last promise will wait until the last promise will resolved?
It seems like you need two separate promise objects:
One for the fb.load event and another one for the result of the API call.
Try chaning your code to read -
this.$get = function($q, $window, $rootScope) {
var apiLoaded=$q.defer();
$rootScope.$on("fb.load", function(e, FB) {
apiLoaded.resolve(FB);
});
// You should reject the promise if facebook load fails.
$facebook.api = function () {
var resultDefer = $q.defer(),
args=arguments;
args[args.length++] = function(response) {
$rootScope.$apply(function() {
resultDefer.resolve(response);
// you should reject if the response is an error
});
};
return apiLoaded.promise.then(function(FB) {
FB.api.apply(FB, args);
return resultDefer.promise;
});
};
return $facebook;
};
Also note that whenever you call resolve() from non-angularish code, you will need to wrap it with $rootScope.$apply(), otherwise then promise 'then' handlers will not get executed. Good luck!
Related
I have a controller that performs a http request.
This request can take anywhere between 2 seconds to 4 minutes to return some data .
I have added a button, that users should click to cancel the request if searches take too long to complete.
Controller:
$scope.search = function() {
myFactory.getResults()
.then(function(data) {
// some logic
}, function(error) {
// some logic
});
}
Service:
var myFactory = function($http, $q) {
return {
getResults: function(data) {
var deffered = $q.dafer();
var content = $http.get('someURL', {
data: {},
responseType: json
)}
deffered.resolve(content);
returned deffered.promise;
}
}
}
Button click:
$scope.cancelGetResults = function() {
// some code to cancel myFactory.getResults() promise
}
How can I implement a button click to cancel the myFactory.getResults() promise?
The question uses deferred antipattern which usually should be avoided, but it fits the cancellation case:
getResults: function(data) {
var deffered = $q.defer();
$http.get('someURL', {
data: {},
responseType: json
}).then(deffered.resolve, deferred.reject);
deffered.promise.cancel = function () {
deferred.reject('CANCELLED')
};
returned deffered.promise;
}
getResult is a service in which we are implementing cancellation.
getResult = function(){
var deferred = $q.defer();
$http.get(url).success(function(result){
deffered.resolve(result);
}).error(function(){
deffered.reject('Error is occured!');
});
return deferred.promise;
};
where url variable is used in place of any Restful API url. You can use it with given code.
getResult().then(function (result) { console.log(result); };
You could use .resolve() method which should be available.
Pass the promise in the controller to the variable.
Create e.g. cancel method which takes the promise as an argument in you factory. Then call this method in the cancelGetResults() function in the controller.
In the cancel method you just call .resolve on the passed promise.
This should actually do.
https://www.bennadel.com/blog/2731-canceling-a-promise-in-angularjs.htm
I'm fairly new to AngularJS and have just begun to grasp many of the concepts I especially like the MVC design pattern. But I am having a difficult time implementing the Service layer in my application.
What I am finding is that after my Controller calls a method within the service, it continues with code execution before the service returns the data; so that by the time the service does return the data, it isn't of any use to the controller.
To give a better example of what I'm saying, here is the code:
var InsightApp = angular.module('InsightApp', ['chart.js']);
// Module declaration with factory containing the service
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function () {
return $http.get('Home/GetSalesData')
.then(function (result) {
return result.data;
});
}
};
});
InsightApp.controller("ChartSelectionController", GetAvailableCharts);
// 2nd Controller calls the Service
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections();
// This method is executed before the service returns the data
function workWithData(response){
// Do Something with Data
}
}
All the examples I've found seem to be constructed as I have here or some slight variation; so I am not certain what I should be doing to ensure asynchronous execution.
In the Javascript world, I'd move the service to the inside of the Controller to make certain it executes async; but I don't how to make that happen in Angular. Also, it would be counter intuitive against the angular injection to do that anyway.
So what is the proper way to do this?
http return a promise not the data, so in your factory your returning the $http promise and can use it like a promise with then, catch, finally method.
see: http://blog.ninja-squad.com/2015/05/28/angularjs-promises/
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections()
.then(function(res) {
// execute when you have the data
})
.catch(function(err) {
// execute if you have an error in your http call
});
EDIT pass params to model service:
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function (yourParameter) {
console.log(yourParameter);
return $http.get('Home/GetSalesData')
.then(function (result) {
return result.data;
});
}
};
});
and then :
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections('only pie one')
.then(function(res) {
// execute when you have the data
})
.catch(function(err) {
// execute if you have an error in your http call
});
You should proceed like this :
DataService.GetChartSelections().then(function (data) {
workWithData(data);
}
Actually $http.get returns a Promise. You can call the method then to handle the success or failure of your Promise
Should it not be like this, when your $http returns a promise or you pass a callback.
With passing callback as a param.
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function (workWithData) {
return $http.get('Home/GetSalesData')
.then(function (result) {
workWithData(result.data);
});
}
};
});
Controller code:
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections(workWithData);
// This method is executed before the service returns the data
function workWithData(response){
// Do Something with Data
}
}
Or use then or success:
var response = DataService.GetChartSelections().then(function(res){
//you have your response here... which you can pass to workWithData
});
Return the promise to the controller, dont resolve it in the factory
var InsightApp = angular.module('InsightApp', ['chart.js']);
// Module declaration with factory containing the service
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function () {
return $http.get('Home/GetSalesData');
}
};
});
In the controller,
var successCallBk =function (response){
// Do Something with Data
};
var errorCallBK =function (response){
// Error Module
};
var response = DataService.GetChartSelections().then(successCallBk,errorCallBK);
I am using AngularJS to call an http service that returns some opening times in an object. I don't understand why, in my controller, the console.log is printed 4 times, instead of one time. Can anyone explain this to me?
Here is my service/factory code:
myApp.factory('BookingFactory', ['$http', '$q', function($http, $q) {
var deferredTime = $q.defer();
return {
GetDealerLocationTimeList: function(websiteId) {
return $http.get('/d/GetDealerLocationTimes?website_id=' + websiteId)
.then(function(response) {
deferredTime.resolve(response.data);
dealerLocationTimeList.push(response.data);
return deferredTime.promise;
}, function(error) {
deferredTime.reject(response);
return deferredTime.promise;
});
}
}
}]);
Here is my controller code that is calling the service:
var promise = BookingFactory.GetDealerLocationTimeList(website_id);
promise.then(
function(da) {
$scope.dealerLocationTimeList = da;
console.log($scope.dealerLocationTimeList);
},
function(error) {
$log.error('failure loading dealer associates', error);
}
);
There are many mistakes in this code ><
If you want to use deferred, then this should be the code:
myApp.factory('BookingFactory', ['$http', '$q', function($http, $q) {
return {
GetDealerLocationTimeList: function(websiteId) {
var deferredTime = $q.defer(); // deferred should be created each time when a function is called. It can only be consumed (resolved/rejected) once.
/* return - don't need to return when you already creating a new deferred*/
$http.get('/d/GetDealerLocationTimes?website_id=' + websiteId)
.then(function(response) {
deferredTime.resolve(response.data);
// dealerLocationTimeList.push(response.data);
}, function(error) {
deferredTime.reject(error); // it should be 'error' here because your function argument name says so...
});
return deferredTime.promise; // promise is returned as soon as after you call the function, not when the function returns
}
}
}]);
But it is a better practice to return the promise if your inner function is a promise itself (like $http.get)
myApp.factory('BookingFactory', ['$http', '$q', function($http, $q) {
return {
GetDealerLocationTimeList: function(websiteId) {
// no need to create new deferred anymore because we are returning the promise in $http.get
return $http.get('/d/GetDealerLocationTimes?website_id=' + websiteId)
.then(function(response) {
// dealerLocationTimeList.push(response.data);
return response.data; // return the data for the resolve part will make it available when the outer promise resolve
}/* this whole part should be omitted if we are not doing any processing to error before returning it (thanks #Bergi)
, function(error) {
return $q.reject(error); // use $q.reject to make this available in the reject handler of outer promise
}*/);
// no need to return promise here anymore
}
}
}]);
You can see I've also commented your dealerLocationTimeList.push(response.data). In this case you should push the data into your scope variable on the outer layer (in the promise.then), because dealerLocationTimeList is not available in the factory.
promise.then(
function(da) {
// you might want to do an isArray check here, or make sure it is an array all the time
$scope.dealerLocationTimeList.push(da);
console.log($scope.dealerLocationTimeList);
},
...
);
I've got a service that has the following method (among others), which returns an $http promise
function sessionService($http, serviceRoot) {
return {
getAvailableDates: function () {
return $http.get(serviceRoot + '/session/available_dates');
}
};
};
angular.module('app').service('sessionService', ['$http', 'serviceRoot', sessionService]);
And then another factory that kinda wraps it and caches/adds data to localStorage. This returns a regular promise
angular.module('app')
.factory('AvailableDates', AvailableDates);
AvailableDates.$inject = ['sessionService', '$window', '$q'];
function AvailableDates(sessionService, $window, $q) {
var availableDates = [];
return {
getAvailableDates: getAvailableDates
};
function getAvailableDates() {
var deferred = $q.defer();
var fromStorage = JSON.parse($window.sessionStorage.getItem('validDates'));
if (availableDates.length > 0) {
deferred.resolve(availableDates);
} else if (fromStorage !== null) {
deferred.resolve(fromStorage);
} else {
sessionService.getAvailableDates()
.success(function (result) {
availableDates = result;
$window.sessionStorage.setItem('validDates', JSON.stringify(availableDates));
deferred.resolve(availableDates);
});
}
return deferred.promise;
}
}
This all works fine. My problem is I can't figure out how to test this thing while mocking the sessionService. I've read all the related stackoverflow questions, and tried all kinds of different things, to no avail.
Here's what my test currently looks like:
describe('testing AvailableDates factory', function () {
var mock, service, rootScope, spy, window, sessionStorageSpy, $q;
var dates = [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
var result;
beforeEach(module('app'));
beforeEach(function() {
return angular.mock.inject(function (_sessionService_, _AvailableDates_, _$rootScope_, _$window_, _$q_) {
mock = _sessionService_;
service = _AvailableDates_;
rootScope = _$rootScope_;
window = _$window_;
$q = _$q_;
});
});
beforeEach(inject(function () {
// my service under test calls this service method
spy = spyOn(mock, 'getAvailableDates').and.callFake(function () {
return {
success: function () {
return [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
},
error: function() {
return "error";
}
};
});
spyOn(window.sessionStorage, "getItem").and.callThrough();
}));
beforeEach(function() {
service.getAvailableDates().then(function(data) {
result = data;
// use done() here??
});
});
it('first call to fetch available dates hits sessionService and returns dates from the service', function () {
rootScope.$apply(); // ??
console.log(result); // this is printing undefined
expect(spy).toHaveBeenCalled(); // this passes
expect(window.sessionStorage.getItem).toHaveBeenCalled(); // this passes
});
});
I've tried various things but can't figure out how to test the result of the AvailableDates.getAvailableDates() call. When I use done(), I get the error:
Timeout - Async callback was not invoked withing timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL (I've tried overriding this value, no luck).
If I take out the done(), and just call rootScope.$apply() after the .then is called, I get an undefined value as my result.
What am I doing wrong?
I see more issues in your example.
The main problem is the success definition in the mock. Success is a function, which has a function as a parameter - callback. Callback is called when data is received - data is passed as the first argument.
return {
success: function (callback) {
callback(dates);
}
};
Simplified working example is here http://plnkr.co/edit/Tj2TZDWPkzjYhsuSM0u3?p=preview
In this example, mock is passed to the provider with the module function (from ngMock) - you can pass the object with a key (service name) and value (implementation). That implementation will be used for injection.
module({
sessionService:sessionServiceMock
});
I think test logic should be in one function (test), split it into beforeEach and test is not a good solution. Test is my example; it's more readable and has clearly separated parts - arrange, act, assert.
inject(function (AvailableDates) {
AvailableDates.getAvailableDates().then(function(data) {
expect(data).toEqual(dates);
done();
});
rootScope.$apply(); // promises are resolved/dispatched only on next $digest cycle
expect(sessionServiceMock.getAvailableDates).toHaveBeenCalled();
expect(window.sessionStorage.getItem).toHaveBeenCalled();
});
Is it possible to pass a promise to a UI.Router $state from an outside controller (e.g. the controller that triggered the state)?
I know that $state.go() returns a promise; is it possible to override that with your own promise resolve this promise directly yourself or resolve it using a new promise?
Also, the documentation says the promise returned by $state.go() can be rejected with another promise (indicated by transition superseded), but I can't find anywhere that indicates how this can be done from within the state itself.
For example, in the code below, I would like to be able to wait for the user to click on a button ($scope.buttonClicked()) before continuing on to doSomethingElse().
I know that I can emit an event, but since promises are baked into Angular so deeply, I wondered if there was a way to do this through promise.resolve/promise.reject.
angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('myState', {
template: '<p>myState</p>',
controller: ['$state', '$scope', '$q', function ($state, $scope, $q) {
var deferred = $q.defer();
$scope.buttonClicked = function () {
deferred.resolve();
}
}]
});
}])
.controller('mainCtrl', ['$state', function ($state) {
$state.go('myState')
.then(doSomethingElse)
}]);
Update
I have accepted #blint's answer as it has got me closest to what I wanted. Below is some code that fleshes out this answer's idea a bit more. I don't think the way I have written this is a very elegant solution and I am happy if someone can suggest a better way to resolve promises from a triggered state.
The solution I've chosen is to chain your promises as you normally would in your controller, but leave a $scope.next() method (or something similar) attached to that scope that resolves/rejects the promise. Since the state can inherit the calling controller's scope, it will be able to invoke that method directly and thus resolve/reject the promise. Here is how it might work:
First, set up your states with buttons/controllers that call a $scope.next() method:
.config(function ($stateProvider) {
$stateProvider
.state('selectLanguage', {
template: '<p>Select language for app: \
<select ng-model="user.language" ng-options="language.label for language in languages">\
<option value="">Please select</option>\
</select>\
<button ng-click="next()">Next</button>\
</p>',
controller: function ($scope) {
$scope.languages = [
{label: 'Deutch', value: 'de'},
{label: 'English', value: 'en'},
{label: 'Français', value: 'fr'},
{label: 'Error', value: null}
];
}
})
.state('getUserInfo', {
template: '<p>Name: <input ng-model="user.name" /><br />\
Email: <input ng-model="user.email" /><br />\
<button ng-click="next()">Next</button>\
</p>'
})
.state('mainMenu', {
template: '<p>The main menu for {{user.name}} is in {{user.language.label}}</p>'
})
.state('error', {
template: '<p>There was an error</p>'
});
})
Next, you set up your controller. In this case, I'm using a local service method, user.loadFromLocalStorage() to get the ball rolling (it returns a promise), but any promise will do. In this workflow, if the $scope.user is missing anything, it will progressively get populated using states. If it is fully populated, it skips right to the main menu. If elements are left empty or are in an invalid state, you get taken to an error view.
.controller('mainCtrl', function ($scope, $state, $q, User) {
$scope.user = new User();
$scope.user.loadFromLocalStorage()
.then(function () {
var deferred;
if ($scope.user.language === null) {
deferred = $q.defer();
$state.go('selectLanguage');
$scope.next = function () {
$scope.next = undefined;
if ($scope.user.language === null) {
return deferred.reject('Language not selected somehow');
}
deferred.resolve();
};
return deferred.promise;
}
})
.then(function () {
var deferred;
if ($scope.user.name === null || $scope.user.email === null) {
deferred = $q.defer();
$state.go('getUserInfo');
$scope.next = function () {
$scope.next = undefined;
if ($scope.user.name === null || $scope.user.email === null) {
return deferred.reject('Could not get user name or email');
}
deferred.resolve();
};
return deferred.promise;
}
})
.then(function () {
$state.go('mainMenu');
})
.catch(function (err) {
$state.go('error', err);
});
});
This is pretty verbose and not yet very DRY, but it shows the overall intention of asynchronous flow control using promises.
The purpose of promises is to guarantee a result... or handle a failure. Promises can be chained, returned in functions and thus extended.
You would have no interest in "overriding" a promise. What you can do, however:
Handle the failure case. Here's the example from the docs:
promiseB = promiseA.then(function(result) {
// success: do something and resolve promiseB
// with the old or a new result
return result;
}, function(reason) {
// error: handle the error if possible and
// resolve promiseB with newPromiseOrValue,
// otherwise forward the rejection to promiseB
if (canHandle(reason)) {
// handle the error and recover
return newPromiseOrValue;
}
return $q.reject(reason);
});
Append a new asynchronous operation in the promise chain. You can combine promises. If a method called in the chain returns a promise, the parent promised will wall the rest of the chain once the new promise is resolved.
Here's the pattern you might be looking for:
angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('myState', {
template: '<p>myState</p>',
controller: 'myCtrl'
});
}])
.controller('myCtrl', ['$scope', '$state', '$q', '$http', 'someAsyncServiceWithCallback',
function ($scope, $state, $q, $http, myService) {
$scope.buttonClicked = function () {
$state.go('myState')
.then(function () {
// You can return a promise...
// From a method that returns a promise
// return $http.get('/myURL');
// Or from an old-school method taking a callback:
var deferred = $q.defer();
myService(function(data) {
deferred.resolve(data);
});
return deferred.promise;
},
function () {
console.log("$state.go() failed :(");
});
};
}]);
Perhaps one way of achieving this would be to return your promise from the state's resolve
resolve: {
myResolve: function($scope, $q) {
var deferred = $q.defer();
$scope.buttonClicked = function () {
deferred.resolve();
}
return deferred.promise;
}
}
There is also an example in resolve docs that may be of interest
// Another promise example. If you need to do some
// processing of the result, use .then, and your
// promise is chained in for free. This is another
// typical use case of resolve.
promiseObj2: function($http){
return $http({method: 'GET', url: '/someUrl'})
.then (function (data) {
return doSomeStuffFirst(data);
});
},