Angular promise resolves inside function but not outside - angularjs

I have a recursive function checking for some data every half second or so. The function returns a promise. Once I find the data, I want to resolve the promise and pass the data as the resolution. The problem is, the promise won't call .then() outside of the function. Here's the fiddle: http://jsfiddle.net/btyg1u0g/1/.
Here's the fiddle code:
Service:
myApp.factory('myService', function($q, $timeout) {
var checkStartTime = false;
var checkTimeout = 30000;
function checkForContent() {
var deferred = $q.defer();
// simulating an $http request here
$timeout(function () {
console.log("Checking...");
if (!checkStartTime) checkStartTime = new Date();
// this would normally be 'if (data)'
if ((new Date()) - checkStartTime > 3000) {
deferred.resolve("Finished check");
checkStartTime = false; // reset the timeout start time
} else {
// if we didn't find data, wait a bit and check again
$timeout(function () {
checkForContent();
}, 400);
}
}, 5);
// then is called in here when we find data
deferred.promise.then(function(message) {
console.log("Resolved inside checkForContent");
});
return deferred.promise;
}
return {
runCheck: function() {
return checkForContent()
}
}
});
Controller:
myApp.controller('MyCtrl', function ($scope, myService) {
$scope.name = 'Superhero';
// then is never called
myService.runCheck()
.then(function (message) {
console.log("Resolved outside checkForContent");
});
});

Check out this fiddle.
The outter $timeout function is named so it can be called from within itself.
$timeout(function inner() {
// ...
Then it is called recursively like this:
$timeout(function () {
inner();
}, 400);

Related

Pass parameters from $get promise response to $scope

I do a ajax (get) request and obtain a promise with json data in a angular "jobList" service.
Then I update the scope with the obtained data. But my problem is that to update a scope variable 'X' I need to create a function per variable "readX" (see bellow).
Is there a way to add parameters, like in the last function in the following code?
app.controller("JobListController", ['$scope', '$timeout', 'jobList',
function ($scope, $timeout, jobList) {
var readList = function (response) {
if (response) {
$timeout(function () {
$scope.list = response;
$scope.$apply();
});
}
};
var readFamilies = function (response) {
if (response) {
$timeout(function () {
$scope.allFamilies = response;
$scope.$apply();
});
}
};
var readRegions = function (response) {
if (response) {
$timeout(function () {
$scope.allRegions = response;
$scope.$apply();
});
}
};
// !!! ----- HERE ------------- !!!
var readSomething = function (response, something) {
if (response) {
$timeout(function () {
$scope[something] = response;
$scope.$apply();
});
}
};
jobList.get().then(readList);
jobList.getAll("allFamilies").then(readFamilies);
jobList.getAll("allRegions").then(readRegions);
}]);
For a start you could simplify those callback functions: provided the callback happens inside angular (and it you're using $http it will) you don't need the $timeout call nor the $scope.$apply() call. Also you should write your service to only succeed if it returns data, reject the promise if it fail;s and that way you don't need the if So each callback could just be the assignment.
If you are making multiple calls that return promises, can you collapse the calls together?
$q.all([jobList.get(), jobList.getAll("allFamilies"), jobList.getAll("allRegions")])
.then(([list, families, regions]) => {
$scope.list = list;
$scope.allFamilies = families;
$scope.allRegions = regions;
});
I used es6 syntax here: it's well worth setting up your build chain to use something like babeljs so you can use the shorthand notation for simple callbacks.
If you really want to make the calls separately (they still evaluate in parallel) you can write a factory to generate the callbacks:
function assignToScope(name) {
return success;
function success(data) {
$scope[name] = data;
}
}
jobList.get().then(assignToScope('list'));
jobList.getAll("allFamilies").then(assignToScope('allFamilies'));
jobList.getAll("allRegions").then(assignToScope('allRegions'));
you could save the required property in a scope variable, before getting the data.
Something like this:
$scope.property = "list";
jobList.get().then(readSomething);
and your function would now become:
var readSomething = function (response) {
if (response) {
$timeout(function () {
$scope[$scope.property] = response;
$scope.$apply();
});
}
};
P.S:
I guess you can also use closures to do something like this:
var readSomething = function (something) {
return function(response){
if (response) {
$timeout(function () {
$scope[something] = response;
$scope.$apply();
});
}
}
};
Try this:
jobList.get().then(function (response) {
readSomething(response);
});
And function readSomething can have response only as in input.

How can I invoke a function after two or more $scope events have been received?

For example, let's assume I need to run a function after receiving two events "eventA" and "eventB". What I usually do is to declare for each event a boolean variable, set the variable to true when the event is received, and ask if the other variable is true to run the function:
var a = false,
b = false;
$scope.$on("eventA", function(){
a = true;
if (b)
performTask();
});
$scope.$on("eventB", function(){
b = true;
if (a)
performTask();
});
var performTask = function() {
/* do something... */
};
This gets more complex if there are three or more events. Is there a design pattern to handle these cases?
You can use $q promises.
var dfdATask= $q.defer();
var dfdBTask= $q.defer();
$scope.$on("eventA", function(){
// whatever this function does
dfdATask.resolve(true);//or pass a value
});
$scope.$on("eventB", function(){
//whatever this function does
dfdBTask.resolve(true);//or pass a value
});
$q.all([dfdATask.promise, dfdBTask.promise]).then(function(){
//be sure to pass in an array of promises
//perform task
})
So theory wise if you only want to execute this magical action after you've received these two events have been called at least once then you probably want to use promises.
app.controller('ExampleOneController', [
'$log',
'$scope',
'$q',
'$rootScope',
function ($log, $scope, $q, $rootScope) {
$scope.anotherAction1FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-1-a', function () {
$log.log('Fired e-1-a');
aDeferred.resolve();
});
$scope.$on('e-1-b', function () {
$log.log('Fired e-1-b');
bDeferred.resolve();
});
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 1!');
$scope.anotherAction1 = 'Hello World 1!';
$scope.anotherAction1FiredCount++;
});
}
]);
That being said usually I want to execute everytime two things happen so I tend to 'reset' my promises.
app.controller('ExampleTwoController', [
'$log',
'$scope',
'$q',
function ($log, $scope, $q) {
$scope.anotherAction2FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-2-a', function () {
$log.log('Fired e-2-a');
aDeferred.resolve();
});
$scope.$on('e-2-b', function () {
$log.log('Fired e-2-b');
bDeferred.resolve();
});
var wait = function () {
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 2!');
$scope.anotherAction2 = 'Hello World 2!';
$scope.anotherAction2FiredCount++;
aDeferred = $q.defer();
bDeferred = $q.defer();
wait();
});
};
wait();
}
]);
Here's the working plunker!
Promises are life.
active polling using $scope.$watch:
One way to do this:
var a = false, b = false;
$scope.$on("eventA", function(){ a = true; });
$scope.$on("eventB", function(){ b = true; });
$scope.$watch(
function() { return a && b; },
function(newval, oldval) {
if (newval) { performTask(); }
}
);
one step further:
var events = { a: false, b: false };
$scope.$on("eventA", function(){ events.a = true; });
$scope.$on("eventB", function(){ events.b = true; });
$scope.$watch(
function() {
var result = true;
for (var key in events) {
result = result && events[key];
}
return result;
},
function(newval, oldval) {
if (newval) { performTask(); }
}
);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
Be sure to read the developer guide and check the "Scope $watch Performance Considerations" section.
regular callback:
var events = { a: false, b: false };
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
performTask();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with one promise, $q.defer():
var events = { a: false, b: false };
var shouldPerform = $q.defer();
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
shouldPerform.resolve();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
shouldPerform.promise.then(performTask);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with multiple promises...
Already been covered by multiple answers.
Promises are meant for your use case. But since you mentioned you were looking for a design pattern, I'll put down one way to do this using the Observer pattern.
You can check out this live Plunkr: http://plnkr.co/edit/1Oqn2TAGTr7NLYZd9ax1?p=preview
Has an angularjs service that handles the logic for tracking events and calling the final action.
The controller simply defines your events, the final event and registers them with your service.
app.controller('MainCtrl', function($scope, EventService) {
var events = [];
... //define events
EventService.registerEvents(events);
EventService.registerEventsCallback(finalEvent); //the observer
});
The service makes this work by removing a called event from the events list upon first execution.
app.factory('EventService', function(){
var events = [];
var finalEvent;
var eventsCallback = function(){
if(!events.length){
finalEvent();
}
}
var resolveEvent= function(event){
var eventIndex = events.indexOf(event);
if(eventIndex>=0){
events.splice(eventIndex,1);
}
}
return{
registerEvents: function(eventsList){
events = angular.copy(eventsList);
},
registerEventsCallback: function(event){
finalEvent = event;
},
publishEvent: function(event){
event();
resolveEvent(event);
eventsCallback();
}
}
});

AngularJS: looping ajax requests, fetch data to scope when done

In my controller, I'm calling a factory that fetches data from an API, then in the success function, the data is passed to another function that loops through it and calls another factory each round in the loop. The data retrieved from the second factory is then mashed up with the data from the first factory in a new object, each round in the loop, and pushed to an array.
When I add the data to the $scope, the view is being updated as the data is being fetched, you can see that the items in the view are added one by one and not all in a bunch.
I also need to sort the data in the $scope before it hits the view.
Is there a way to do this when all actions are finished? – When all data is fetched.
app.controller('MyCtrl', function($scope, firstFactory, secondFactory) {
var objArray = [];
function doStuff() {
firstFactory.getData().success(function(data) {
doMore(data);
});
}
function doMore(data) {
$.each(data, function(key, value) {
secondFactory.getData(value).success(function(result) {
var obj = {
test: result.test,
test2: value.test
};
objArray.push(obj);
});
});
$scope.data = objArray;
}
});
For the second call you can use $q.all. This would get resolved only when all the calls are complete
function doMore(data) {
var promises = [];
$.each(data, function (key, value) {
promises.push(secondFactory.getData(value));
});
$q.all(promises).then(function (responseArray) {
$.each(responseArray, function (result) {
var obj = {
test: result.test
};
objArray.push(obj);
});
$scope.data = objArray;
});
}
You can chain promises. A promise in the chain is only run when the previous has executed, or you can even run promises in parallel and with $q.all wait for all promises to finish.
Here is a sample that might help you plunker:
var app = angular.module('plunker', []);
app.factory('dataFactory', function($http, $q) {
var getData = function() {
var combinedData = [];
var deferred = $q.defer();
$http.get('first.json')
.then(function(data) {
return data;
})
.then(function(data) {
firstData = data;
$http.get('second.json').then(function(secondData) {
angular.forEach(firstData.data, function(value, key) {
combinedData.push(value);
});
angular.forEach(secondData.data, function(value, key) {
combinedData.push(value);
});
deferred.resolve(combinedData);
});
});
return deferred.promise;
};
return {
getData: getData
};
});
app.controller('MainCtrl', function($scope, dataFactory) {
$scope.items = [];
var onSuccess = function(data) {
$scope.items = data;
};
var onError = function() {
console.log('error');
};
dataFactory.getData()
.then(onSuccess, onError);
});

$interval making $http call, which promise is returned for chaining?

I have an angular service and a controller interacting. The service usings the $interval to poll the server. I know this returns a promise, however it uses $http to make an call to the server, which ALSO returns a promise and the chaining of the promises is not happening the way I would expect.
SERVICE
(function () {
'use strict';
var serviceId = "notificationService";
angular.module('app').factory(serviceId, ['helpersService', '$interval', '$http', function (helpersService, $interval, $http) {
var defaultOptions = {
url: undefined,
interval: 1000
};
var myIntervalPromise = undefined;
var displayedNotifications = [];
function onNotificationSuccess(response) {
//alert("in success");
displayedNotifications.push(response.data);
return response.data;
}
function onNotificationFailed(response) {
alert("in Failure");
throw response.data || 'An error occurred while attempting to process request';
}
function initializeNotificationService(configOptions) {
var passedOptions = $.extend({}, defaultOptions, configOptions);
if (passedOptions.url) {
myIntervalPromise = $interval(
function() {
//console.log(passedOptions.url);
//return helpersService.getAjaxPromise(passedOptions);
//promise.then(onNotificationSuccess, onNotificationFailed);
$http({
method: 'POST',
url: passedOptions.url
}).then(onNotificationSuccess, onNotificationFailed);
}, passedOptions.interval);
//alert("in initializeNotificationService");
return myIntervalPromise;
}
//return myIntervalPromise;
}
//$scope.$on('$destroy', function() {
// if (angular.isDefined(myIntervalPromise)) {
// $interval.cancel(myIntervalPromise);
// myIntervalPromise = undefined;
// }
//});
return {
// methods
initializeNotificationService: initializeNotificationService,
//properties
displayedNotifications : displayedNotifications
};
}]);
})();
CONTROLLER
(function () {
'use strict';
var controllerId = 'MessageCtrl';
//TODO: INVESTIGATE HAVING TO PASS $INTERVAL TO HERE TO DESTROY INTERVAL PROMISE.
//TODO: HAS TO BE A WAY TO MOVE THAT INTO THE SERVICE
angular.module('app').controller(controllerId, ['notificationService', '$scope', '$interval', function (notificationService, $scope, $interval) {
var vm = this;
// tied to UI element
vm.notifications = [];
vm.initialize = function () {
// initialize tyhe notification service here
var intervalPromise = notificationService.initializeNotificationService({ url: 'api/userProfile/getNotifications', interval: 5000 });
intervalPromise.then(
function (response) {
// NEVER GETS CALLED
var s = "";
//vm.notifications.push(response);
// alert("successful call");
},
function (response) {
var s = "";
// THIS GETS CALLED WHEN THE PROMISE IS DESTROYED
// response = canceled
//alert("failure to call");
},
function(iteration) {
console.log(notificationService.displayedNotifications);
// This gets called on every iteration of the $interval in the service
vm.notifications = notificationService.displayedNotifications;
}
);
// TODO: SEE COMMENT AT TOP OF CONTROLLER
$scope.$on('$destroy', function () {
if (angular.isDefined(intervalPromise)) {
$interval.cancel(intervalPromise);
intervalPromise = undefined;
}
});
};
vm.alertClicked = function (alert) {
alert.status = 'old';
};
// call to init the notification service here so when the controller is loaded the service is initialized
vm.initialize();
}]);
})();
The way this ends up flowing, and I'll do my best to show flow here
1) SERVICE - $interval makes the call with the $http BOTH OF THESE SEEM TO RETURN THEIR OWN PROMISES ACCORDING TO THE DOCS
2) CONTROLLER - intervalPromise's NOTIFY callack is called
3) SERVICE - onNotificationSuccess callback of $http is called
WHAT DOESN'T HAPPEN THAT I WOULD EXPECT
4) CONTROLLER - intervalPromise success callback is never called
Should the return response.data in the onNotificationSuccess handler within the service trigger the then chain in the Controller? It's aware that the promise is returned or seemingly cause the notify callback in the controller is called each time $interval executes, so I'm confused as to where the chain is broken.
IDEAL
$interval calls with $http, the promise from $http is passed up to the controller
then with each iteration new messages are added to the service on a successful call by $interval, then in the controller onsuccess I can check the property of the service and update the UI. Where am I losing the method chain?
I would recommend breaking the usage of $interval outside of service and use it directly in your controller.
The service being provided is the ability to get data from the server and the interval is the means in which to get the data, which is more indicative of the user interface's requirements as to how often the data is retrieved.
What you appear to be doing is to wrap the functionality of the $interval service which is causing a complication for you.
Note: after creating a quick plnkr the report progress event of $interval returns the iteration number (times called) and no other parameters.
Ended up with everything in the controller...
(function () {
'use strict';
var controllerId = 'NotificationCtrl';
angular.module('app').controller(controllerId, ['helpersService', '$scope', '$interval', function (helpersService, $scope, $interval) {
var vm = this;
var intervalPromise = undefined;
// tied to UI element
vm.notifications = [];
function onNotificationSuccess(response) {
//alert("in success");
vm.notifications.push.apply(vm.notifications, response.data);
return response.data;
}
function onNotificationFailed(response) {
//alert("in Failure");
throw response.data || 'An error occurred while attempting to process request';
}
vm.initialize = function () {
intervalPromise = $interval(
function () {
var promise = helpersService.getAjaxPromise({ url: 'api/userProfile/getNotifications' });
promise.then(onNotificationSuccess, onNotificationFailed);
}, 5000);
$scope.$on('$destroy', function () {
if (angular.isDefined(intervalPromise)) {
$interval.cancel(intervalPromise);
intervalPromise = undefined;
}
});
};
vm.alertClicked = function (alert) {
//alert.status = 'old';
};
// call to init the notification service here so when the controller is loaded the service is initialized
vm.initialize();
}]);
})();

Angular Service Definition: service or factory

I am an angular newbie, I am building an application, one thing really puzzling me is there are couple of ways of defining a service, and I read more from this link: How to define service
then it seems there is no big difference among the ways of defining a service.
but I just noticed one difference which I think is different:
see this service I get from here http://jsfiddle.net/2by3X/5/
var app = angular.module('myApp', []);
app.service('test', function($timeout, $q) {
var self = this;
this.getSomething = function() {
return self.getData().then(function(data) {
return self.compactData(data);
});
};
this.getData = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("foo");
}, 2000);
return deferred.promise;
};
this.compactData = function(data) {
var deferred = $q.defer();
console.log(data);
$timeout(function() {
deferred.resolve("bar");
}, 2000);
return deferred.promise;
};
});
if I define this service using "factory" like below, one function cannot call other functions of the service.
app.factory('test', function($timeout, $q) {
return {
getSomething : function() {
return getData().then(function(data) {
return compactData(data);
});
},
getData : function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("foo");
}, 2000);
return deferred.promise;
},
compactData : function(data) {
var deferred = $q.defer();
console.log(data);
$timeout(function() {
deferred.resolve("bar");
}, 2000);
return deferred.promise;
},
};
});
I will get this in the browser console:
[08:41:13.701] "Error: getData is not defined
.getSomething#http://fiddle.jshell.net/_display/:47
Ctrl1#http://fiddle.jshell.net/_display/:75
invoke#http://code.angularjs.org/1.0.0/angular-1.0.0.js:2795
instantiate#http://code.angularjs.org/1.0.0/angular-1.0.0.js:2805
Can anyone explain this? thanks.
you have two big problems there:
the factory returns an object with incorrect syntax.
javascript scope of variables is functional
That is,
You should be returning an object like {key: value, key: value}
values can be functions. however, you return {key = value, key = value}
First fix:
return {
getSomething : function() {...},
getData : function...
}
Secondly, not being able to call functions is normal. See this jsfiddle. I mocked everything. You can call one of the functions returned by the service. However, when from getSomething try to call getData, you get "undefined":
app.factory('testSO', function () {
return {
getSomething: function () {
console.log('return getsomething');
getData();
},
getData: function () {
console.log('return getData');
}...
This would be the same as declaring everything in the scope of the factory function and return references see in jsfiddle:
app.factory('testSO', function () {
var getSomething = function () {
console.log('return getsomething');
};
...
return {
getSomething: getSomething,
...
}
and now you can call local functions as shown in the final version of the jsfiddle:
app.factory('testSO', function () {
var getSomething = function () {
console.log('return getsomething');
getData();
};
...
The original service has something important in it: var self = this; . Some people use var that = this. It's a workaround for an error in ECMA.
In the case of the original code, it's used to "put everything in one object". Functions (properties that happen to be functions) in self need a reference to know where the function you want to call is. Try it yourself here http://jsfiddle.net/Z2MVt/7/

Resources