I can’t get my head around some of the promises. They work fine in some instances of my app, but in some they just never work.
In my controller, I have the following command:
myFactory.redrawCategories()
.then(myFactory.redrawTasks());
The redrawTasks function is called instantly, without waiting for the redrawCategories to finish.
The functions inside my factory look like this
redrawTasks: function(){
var defer = $q.defer();
db.getAllDocs("task_").then(function(res) {
angular.forEach(res, function(value){
value = value.doc;
tasks[value.category].push(value);
});
angular.forEach(tasks, function(taskArray, cat){
// some code
});
defer.resolve(1);
});
return defer.promise;
},
The other one is like
redrawCategories: function(){
var deferred = $q.defer();
db.getAllDocs("category_").then(function(res) {
var categoryArray = [];
angular.forEach(res, function(value){
categoryArray.push(value.doc);
});
deferred.resolve("done");
});
return deferred.promise;
},
Some of the unimortant code has been removed for better overview.
No idea really how to do it. I've tried putting the resolve() function just in front of the return but that doesn't work either.
I've read that sometimes you have to wrap things in a $scope.$apply, well in this case most likely a $rootScope.$apply as it's in the factory outside of the controller scope, but that doesn't really change it either, besides I haven't really grasped when something is "outside of Angular" as they describe that.
I've read a lot of examples and tutorials, but I just don't see the forest for the trees anymore.
Any help would be appreciated a lot. I'm so stuck with this :/ Thanks
.then expects a function reference.
myFactory.redrawCategories()
.then(myFactory.redrawTasks());
should be
myFactory.redrawCategories()
.then(myFactory.redrawTasks);
When you have the () the function is executed immediately and .then is passed whatever it returned.
As noted in the comments, if you're relying on this in redrawTasks you'd do
myFactory.redrawCategories()
.then(function() {
return myFactory.redrawTasks();
});
Try:
myFactory.redrawCategories()
.then(myFactory.redrawTasks);
You were calling the function. Instead you just want to pass it to then function and have angular call it.
Related
So I have this problem. I'm fairly new to angular and I've been told to modify a directive which manages forms to make the submit button disabled then enabled again when all the work is done.
Since the functions being called usually have async calls, simply adding code sequentially doesn't work.
var ngSubmit = function() {
vm.disabled = true;
$scope.ngSubmitFunction();
vm.disabled = false;
}
Button is enabled before async calls under ngSubmitFunction() finish.
So I thought a promise would fix that and made something like:
var promise = function() {
return $q(function (resolve) {$scope.ngSubmitFunction()});
}
var ngSubmit = function() {
vm.disabled = true;
promise().then(function() {
vm.disabled = false;
});
}
This doesn't output any error but never enables the button again (.then is never called).
Tried different kind of promises declaration, all with the same result, except for this one:
$scope.submitPromise = function() {
return $q.when($scope.ngSubmitFunction());
}
This does call .then function, but again, doesn't wait for any child async function to finish. '.then' is called instantly, like the sequential version.
Have in mind that I don't know what's under ngSubmitFunction(). It is used by dozens developers and it may contain from 0 to multiple async calls. But typical scenario is something like:
ngSubmitFunction() calls func()
-- func() decides wether to call create() or update()
-- -- update() calls a elementFactory.update() which is an async call
-- -- -- elementFactory.update().then(function()) is called when finished.
-- -- -- -- At THIS point, I should enable the button again.
How can I achieve this? is there a way to chain promises with non-promises functions in between? or another way to make a code only execute when everything else is done? I thought about creating an event at DataFactory when an async call is over but if the update() function was calling to more than one async call this wouldn't work.
If you are using promises, your async functions should return promises, if they do it should work like this:
var ngSubmit = function() {
vm.disabled = true;
$scope.ngSubmitFunction().then(() => {
vm.disabled = false;
});
}
I don't know what's under ngSubmitFunction()
Then you're out of luck, promises won't help you here. Promises or $q.when cannot magically inspect the call and see what asynchronous things it started or even wait for them - ngSubmitFunction() needs to return a promise for its asynchronous results itself.
You need to make every function in your codebase which (possibly) does something asynchronous that needs to be awaitable return a promise. There's no way around this.
Well, in case someone wonders, we haven't found a solution to that (there probably isn't). So we will go with the adding returns to all the chain of functions to make sure the ngSubmitFunction recieves a promise and therefor can wait for it to finish before calling '.then'. Not only this makes it work for the cases where there's only one promise implied but it is also a good programming practice.
Cases with more than one promise are scarce so we will treat them individually on the controller itself.
Thank you all for your comments.
Disclaimer: there actually two questions being asked here but I feel like they are closely related.
I'm trying to pass a promise object to a directive and I want to run some initialization code in the directive as soon as the promise resolves.
In a controller I have:
$scope.item = $http.get(...)
.success(function (result) {
$scope.item = result.item;
});
$scope.item is passed to a directive (via an attribute in an isolated scope called item).
The directive link function finally do something like:
Promise.resolve(scope.item)
.then(function () {
initialize();
});
This works fine in Firefox but when I run it on IE I get an error because Promise is not defined. This problem made me realize I need to probably use the AngularJS $q service in order to provide consistency among browsers and while I was looking at the documentation I discovered another problem, which seemed small at first but it is actually giving me headaches: the success() function is deprecated and I should use then(successCallback) instead. Easy peasy, I thought, BUT as soon as I change the success call in the controller the code stop working in Firefox too! I cannot figure out why. So this is the first question.
The second question is that (even if I leave the success call in the controller) if I modify the code in the directive link function to use $q with what I thought was the equivalent:
$q.resolve(scope.item, function() { initialize(); });
this still doesn't work at all. Any suggestion?
You need to use Angular's $q not only because it works across browsers - but also because it is deeply linked to Angular's digest cycles. Other promise libraries can accomplish this feat but native promises cannot easily do so.
What $q.when does (the $q version of Promise.resolve) is convert a value or a promise to a $q promise. You don't need to do it since you're already using Angular's own $http API which returns promises already.
I warmly recommend that you put your web calls in services and don't affect the scope directly - and then call those services to update the scope.
The pattern is basically:
$http.get(...) // no assign
.success(function (result) { // old API, deprecated
$scope.item = result.item; // this is fine
});
Or with the better then promise API that has the benefits of promises over callbacks like chaining and error handling:
$http.get(...).then(function (result) {
$scope.item = result.data;
});
You are correct about the .success method being deprecated. The .then method returns data differently than the .success method.
$scope.httpPromise = $http.get(...)
.then(function (result) {
$scope.item = result.data.item;
return result.data;
});
You need to return the data to chain from it.
$scope.httpPromise.then ( function (data) {
//Do something with data
initialize();
});
For more information on the deprecation of the .success method see AngularJS $http Service API Reference -- deprecation notice.
Since scope.item is a promise all you have to do is:
scope.item.resolve.then(function() { initialize(); });
Make sure $q is injected in your directive.
improved answer
As #benjamin-gruenbaum mentioned correctly, I used an anti-pattern in my answer. So the solution is basically to pass the promise to your directive and use it there (as already mentioned in Benjamins answer).
Working jsFiddle: https://jsfiddle.net/eovp82qw/1/
old answer, uses anti-pattern
Sorry if I give you a solution that perhaps differs too much from your code. But maybe you can adopt it to your solution.
My approach would be to create a second promise I handover to your directive. This is imho a cleaner way for resolving the waiting state of the directive and don't reuse the same scope variable for two different tasks.
HTML
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<my-directive promise="promiseFromController"></my-directive>
</div>
</body>
JS
function MyCtrl($scope, $q, $http) {
function init() {
var deferredCall = $q.defer();
// simulated ajax call to your server
// the callback will be executed async
$http.get('/echo/json/').then(function(data) {
console.log('received', data);
deferredCall.resolve(data); //<-- this will resolve the promise you'll handover to your directive
});
// we're return our promise immediately
$scope.promiseFromController = deferredCall.promise;
}
init();
}
angular.module('myApp',[])
.controller('MyCtrl', MyCtrl)
.directive('myDirective', function() {
return {
scope: {
promise: '='
},
controller: function($scope) {
console.log($scope);
$scope.promise.then(function(data) {
console.log('received data in directive', data);
});
}
}
})
Working jsFiddle: https://jsfiddle.net/1ua4r6m0/
(There is no output, check your browser console ;) )
I feel this is a very bad idea as i do not find it anywhere but testing it on my localhost it seems to work. I have a service :
angular
.module('trips.services')
.service('Trip', Trip);
function Trip($http, $q) {
//.....
function create(data) {
var api_url = "/api/trip";
return $http.post(api_url, data).success(function(data) {
mixpanel.track("Trip: created", {}); // track that a new event happened
});
}
}
I also have a controller where i do something like:
Trip.create(data).success(function(data) {
//do something after the trip is created
})
Now as you can see in this example I use both the then promise and the success callback in the service. If i do breakpoints in the debugger the success callback from the service is executed first and then the then clause from the controller. And it is also great for code organization, the common things i do after the service is successful is in the service while the specific things are in the controller. The thing is, i feel it is very non-according to any documentation or examples. Did not really find it somewhere, I discovered it by mistake. It might not work in some cases also?
I know the other options also, the $q library and could deffer in the service but that ends up in much more code.
So, is this a bad idea and why? Any opinions would be very helpful. I do not want to set up a wrong practice.
Using promises consequently is considered a good practice as you already know. To do that, you can simply replace your success-callback with another then:
function create(data) {
var api_url = "/api/trip";
var promise = $http.post(api_url, data);
promise.then(function(data) {
mixpanel.track("Trip: created", {}); // track that a new event happened
});
return promise;
}
Having multiple independent .then()s on one promise is perfectly fine and works as you would expect.
It's not necessarily a bad idea, it's just you using chaining of promises.
The .then function looks like this:
.then(function(success){}, function(error){});
you can see that .success is just shorthand for the first function above (as if you didn't declare the error callback).
Seeing as the promise can be passed around or maybe you have multiple services making the same request and you want just one promise created, then you might like to declare callbacks from multiple areas yet have just one promise resolve.
So is it a bad idea? No. Why? It is a flexible piece of code which is really up to the programmer to use. Can you declare multiple thens? Yes. Should you mix it with success callbacks? You can, but you might just want to stick to one style for consistency. But thats really up to what you're trying to achieve.
Angular documentation: https://docs.angularjs.org/api/ng/service/$http
(Watch out for deprecation, might just want to stick to .then)
I created this plunker.
The gist is this code:
var myObj = function (){
return {
getThis: function (callback) {
return callback("this");
}
}
}
var myDidSome = function () {
return {
DoSome: function (text) {
return text + " is cool";
}
};
};
var subObj = function (objG, didSome) {
var txt = objG.getThis(function (text) {
return didSome.DoSome(text);
});
console.log(txt);
}
new subObj(new myObj, new myDidSome);
So to deconstruct this code, view myObj and myDidSome as services (angular ones). I'm most interested in the myObj.getThis, this is supposed to mimick some $http response. So when its finished call my callback function passing in the parameters obtained from $http.
The thing I keep thinking about though is $http is async. Will my txt property in the subOj (think of this as a controller) class be populated correctly when the data gets back, I assume it will be undefined till then?
In my scenario this is completely synchronous, which obviously works.
So, I would like to know whether async will be an issue here? And whether doing this practice of propagating the return of an object is a good one, or doesn't matter, or is bad and I shouldn't do it?
You cannot use return like that with an asynchronous method. You have to do everything within the callback. In your example, the result of didSome.DoSome(text) will never be assigned to txt, not even after the callback has run.
For a simple case like this where you're logging the response, doing it in the callback is an acceptable approach. If you need to do something more complex, however, you should look into promises via $q. These provide a much nicer way to rewrite a long chain of callbacks into a more linear flow.
just trying to get to grips with chaining asynchronous functions together as im learning to use Parse.com
I have a function thats like:
delete_post(post_id)
.then(function (){
_this.show_posts(thread_id);
}).then(function (){
_this.clear_submit_forum();
})
and it works ok! now when i add a preceding function so it looks like :
show_confirmation_alert("are you absolutely sure you want to delete the post?")
.then(function(){
delete_post(post_id);
}).then(function (){
_this.show_posts(thread_id);
}).then(function (){
_this.clear_submit_forum();
})
my show confirmation function does this sort of thing:
$popup = create_popup(message)
var def = new $.Deferred();
$popup.find(".ok").bind("click", function (){
def.resolve();
});
$popup.find(".cancel").bind("click", function(){
def.reject();
});
$popup.popup();
$popup.popup("open");
return def.promise();
but when i click ok on the popup all the functions start running and therefore its no use. I really want to have a popup that call a chain of functions without adding handler every time it will make my life so much easier. what could possibly cause this. Is there something i'm really missunderstanding about promises? I thought the function in then cannot run until it recieves a resolved promised from the preceding function so why on earth are they not chaining in series? Thanks