I have a promise that I am binding to in my UI. When the promise resolves and the UI element renders I can then click on that UI element. In my controller code where I handle the click I would like to act on the value of the promise. At this point in my angular code I already know that the promise is resolved.. but when I want to get the value its still a promise.
Whats the best way to get the value of the promise... given that I know it must have resolved?
Promises are always promises - and that's how they should be. While it is possible to bind a promise directly to the view, I generally discourage this practice because it lacks transparency and can lead to view flickering. A better solution is to assign a scope value in a then call:
myService.then(function( val ) {
$scope.val = val;
});
When $scope.val is ready, it can always be treated directly as a value - because it is.
That said, if you want to do the promise assignment directly, you just have to treat it like a promise; that is, you need to call promise.then(function () { ... });. This seems like an inconvenience, but it's actually a testament to just how powerful promises are.
If you want some more info on promises, I pontificated on them (and provided code examples) for a recent Google+ Hangout, where I covered the advantages, common uses, and best practices of promises in AngularJS: AngularJS Hangout -- Promises, Promises
Promises, Promises. The idea of never-ending promise chains was specifically addressed, though I don't have a timecode off the top of my head.
Hope this helps!
What's tricky to understand is that you might think that if the Promise is not yet full-filled then maybe if I wait a bit I can test again to see if by now it has a value. But that is not possible because as explained on MDN:
"Callbacks will never be called before the completion of the current run of the JavaScript event loop."
So as long as your currently started function is running, the Promise can not be full-filled. It has no "value" during the current run of the event loop. "Promise" means "I promise to maybe give it to you during the NEXT event-loop run".
You could save that promise to a global variable and then when a user clicks on a button in the browser a new event-loop starts by running your event-handler which can put another "when()" onto the promise such that if the promise is resolved by that time the new when-function takes the value it gets as argument and saves it into a variable from which you can read it. No need to write it to console, to see that it is there.
That for me was the tricky part, I thought yes sure I can make the Promise write to the console, but I can't read from console, so how can I get that value into the rest of my program? Answer: Wait till the current event-loop is over, then you can.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
Related
The problem: Using a this.promise.then(function(){}) function inside a controller method seems to create a $rootScope:infDig loop although the returned value of that method does not change. It's worth noting that the infinite digest loop is gone when removing the .then() line (see code).
The code:
{{ getSelectedProjects() }} in the view and the following method in the attached controller
var vm = this;
vm.getSelectedProjects = function() {
if (angular.isUndefined(vm.hello)) {
vm.hello = "hello"
vm.promise = $q.when("hello i am a resolved promise")
}
vm.promise //In my original code, this promise is returned by a service (MyService.getPromise()). The resolved value of the returned promise may change which is why the service needs to be called at each digest cycle.
.then(function (ans) {
vm.hello = ans;
}); //remove .then(...) function and there is no infinite digest loop anymore
return vm.hello;
}
The question: Why is this creating an infinite digest loop although the returned value does not change ? How can I avoid it (I badly need the result of that promise)?.
My shaky thoughts: I guess it has to do with the fact .then() each time returns a new promise. The documentation does not state it, but if that promise is each time attached to the controller object (vm), this might be considered as a state change of the controller and trigger a new digest loop, which re-evaluates vm.getSelectedProjects() and again attaches a new promise to the controller, etc.
EDIT
My hypothesis in "shaky thoughts" does not seem fully correct or complete, as the problem persists with this code:
vm.getSelectedProjects = function() {
if (angular.isUndefined(vm.hello)) {
vm.hello = "hello"
}
$q.when("hello i am a resolved promise")//this object is not attached to the controller object
.then(function () {});
return vm.hello;
}
Based on the problems encountered and the comments we created coding guidelines for working with promises in our project. Found it interesting to share it as I haven't seen this documented anywhere.
Coding guideline: Working with promises
Use of promises
Promises are amazing objects, however a wrong use can create headaches. Therefore we have a few guidelines :
Do not use promises in views
Do not use promises in controllers, except in functions called by event handlers (click, init, etc.), the controller construction function (=executed only once) or in animations
If a promise result is used in a view, the model (or the manager) should contain a property that is empty by default and set to the resolved value when the promise is resolved. The controller just returns that value.
Implementation
This is the kind of structure that should be in the model, manager, or other service, as close as possible to the data source. NOT in controllers :
var myApiMethod = function() {
//We call "caching" the fact that this method is executed only if $scope.item is undefined.
If (angular.isUndefined($scope.item) {
$scope.item = {}
$http(xxx).then(function(value){
$scope.item = value;
});
return $scope.item
}
}
The controller then just returns the answer of myApiMethod. You might need to check if 'myService.myApiMethod() != {}'.
In the service we can define a caching behaviour (ex: retrieve data from backend every 2 hours, at a certain event clear the cache, etc. ). As long as there is only one single source of truth (coming from the service in this case, not from the controller) concerning the object to be returned you can easilly play around with this.
Argumentation
*Why not to use promises in views or in controller methods called by expression evaluations (e.g., {{ vm.getMyApiMethodResult() }} *:
Including promises in views is a feature that has been desactivated in Angular 1.x
If myApiMethod is put in the controller without the caching and then used in an expression evaluation (e.g., {{ myCtrl.getMyApiMethod() }} ), it will create an infinite digest cycle, because the resolution of the promise will trigger a new digest cycle, that will again call the vm.getMyApiMethod() function, etc. (more info in this SO answer )
Why not put myApiMethod in t he controller with the caching (like implementation above) to make sure myApiMethod is called only once and avoid the infinite digest loop? Three reasons:
if the response of myApiMethod() changes into a new object, $scope.item will not be updated.
Another drawback of this (that we used a lot in the past, you might still see wrong implementations in the code) is that it creates a lot of duplicate code as we need to duplicate this quite heavy code structure (that can become much more complex when several promises are involved) in all controllers calling myApiMethod()
If you "cache" in the controller, you might end up with different controllers returning different answers although they both return the answer from the same service 'myApiMethod' but at different moments in time.
Remember : This guideline only applies to controller functions called in expression evaluations. You might need promises in controller functions called by event handlers (click, etc.) and that's fine because these functions are not called at each of angular's digest cycles.
Angular is loosing my my JSON values, and i'm trying to find out why. I think I need to make a promise or a time out, or maybe apply. I'm not quite sure...
angular version: 1.2.1
galleryApp.controller('GalleryCtrl', function ($scope, $http, $routeParams, $filter){
$http.get('mygallery.json').success(function(data) {
$scope.gallery = data;
//DISPLAY: CONTENTS OF JSON IN OBJECT : WORKS
console.log($scope.gallery);
});
//DISPLAY: undefined : DOES NOT WORK AS EXPECTED
console.log($scope.gallery);
//DISPLAY: CONTENTS OF OBJECT: I can see scope.gallery exists!
//I just can't seem to access scope.gallery directly outside of http.get()
console.log($scope);
});
Note: scope.gallery or "gallery" works perfectly fine in my view! {{gallery.name}} etc.
It seems like there is some behind the scenes things angular is doing to the scope, or some concept that i'm not quite grasping.
Well, it's trivial as $http.get is an asynchronous operation. So while it is working the rest of the code will be finished. If you log the $scope.gallery it is undefined yet. If you log the $scope it's still undefined but will be updated when the success callback is invoked. Th reason of this effect for you is just feature of console.log which writes not the current snapshot but the object itself so if changed the output of the console will be updated respectively. But in general none of your code outside of the $http.get will work as you expected here. So you should either use the success callback or use $watch to track the changes.
Also refer to this documentation: $http, $q
The result of http.$get is a promise (specifically an HtmlPromise, see the $http docs). A promise is described as the following in the $q docs:
an interface for interacting with an object that represents the result of an action that is performed asynchronously, and may or may not be finished at any given point in time
The success method of an HtmlPromise takes a callback function to run once the promise is resolved (meaning the action is complete). The anonymous function thus runs asynchronously. When it is logged outside of the callback, the callback has not yet been run and therefore the scope variable has not been set.
I imagine the view is as expected because the http request completes and the view is updated so fast, it is imperceivable.
I found this code snippet which is part of a angular directive someone wrote for bootstrap modal.
//Update the visible value when the dialog is closed
//through UI actions (Ok, cancel, etc.)
element.bind("hide.bs.modal", function () {
scope.modalVisible = false;
if (!scope.$$phase && !scope.$root.$$phase)
scope.$apply();
});
I understood that this part is for the latter half of two way binding we bind to hide.bs.modal event and update modal when UI changes.
I just wanted to know why is the person checking $$phase for scope and rootScope before calling apply ?
Can't we straightaway call apply ?
What is $$phase here?
I tried searching a lot, couldn't find any good explanation.
EDIT:
I found where I saw the example:
Simple Angular Directive for Bootstrap Modal
$$phase is a flag set while angular is in a $digest cycle.
Sometimes (in rare cases), you want to check $$phase on the scope before doing an $apply. An error occurs if you try to $apply during a $digest:
Error: $apply already in progress
Davin is totally correct that it's a flag that angular sets during the digest cycle.
But don't use it in your code.
I recently got a chance to ask Misko (angular author) about $$phase, and he said to never use it; it's an internal implementation of the digest cycle, and it's not future safe.
To make sure your code continues to work in the future, he suggested wrapping whatever you want to "safe apply" inside of a $timeout
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
This comes up a lot when you have callbacks or other things that might resolve during the digest cycle (but don't always)
Here's an example snippet from when I was dealing with one of google's libraries: (The rest of the service this was from has been left off.)
window.gapi.client.load('oauth2', 'v2', function() {
var request = window.gapi.client.oauth2.userinfo.get();
request.execute(function(response) {
// This happens outside of angular land, so wrap it in a timeout
// with an implied apply and blammo, we're in action.
$timeout(function() {
if(typeof(response['error']) !== 'undefined'){
// If the google api sent us an error, reject the promise.
deferred.reject(response);
}else{
// Resolve the promise with the whole response if ok.
deferred.resolve(response);
}
});
});
});
Note that the delay argument for $timeout is optional and will default to 0 if left unset ($timeout calls $browser.defer which defaults to 0 if delay isn't set)
A little non-intuitive, but that's the answer from the guys writing Angular, so it's good enough for me!
In that example, the element binding will get executed from a non-angular event. In most cases, it is safe to just call $apply() without checking the phase.
If you look at the rest of the code, however, there is a $scope function called showModal(). This function calls into the non-angular code which will likely cause a "hide.bs.modal" event to fire. If the event fires via this route, then the call stack is within a $digest.
So, this event does fall within the rare case of a function that will get called from both angular-managed code AND non-angular code. Checking the $$phase in this case is necessary because you don't know how the event originated. If the $$phase is set to something, then the digest loop will finish to completion and $apply() doesn't need to be called.
This pattern is often referred to as "safe apply".
my understanding is it is good to use when digesting or applying the scope. If it is truthy it means that there is currently a $digest or $apply phase in progress. If you are getting related errors you can do $scope.$$phase || $scope.digest(); which will only digest if $scope.$$pahse is falsy.
You can use $scope.$evalAsync() method rather than using the $scope.$apply() externally with the $$phase value check as rightly suggested by #betaorbust:
I recently got a chance to ask Misko (angular author) about $$phase, and he said to never use it; it's an internal implementation of the digest cycle, and it's not future safe.
The good thing about $scope.$evalAsync() is that it gives you what you really want, get the fresh data up with the digest cycle, faster than even the $timeout can:
Up until now, my approach to deferred-$digest-invocation was to replace the $scope.$apply() call with the $timeout() service (which implicitly calls $apply() after a delay). But, yesterday, I discovered the $scope.$evalAsync() method. Both of these accomplish the same thing - they defer expression-evaluation until a later point in time. But, the $scope.$evalAsync() is likely to execute in the same tick of the JavaScript event loop.
Usage is quite simple:
$scope.$evalAsync(
function() {
// Call some method here OR
$scope.excutemyMethod();
// assign some value;
$scope.someVariable = "asd"
}
);
The method inside the $evalAsync() is added to the Async queue so that it can be executed in the following digest cycle by internally triggering $apply which is what we really want.
Also it includes the $timeout call to handle rarest of rare scenarios watching for the async queue length and waiting the tasks in the Async Queue to be executed to put your function in the execution cycle as soon as possible. Refer Ben Nadel blog, which explains this fact.
I posted an issue on the AngularJS github but it doesn't seem to be getting a whole lot of attention and I wasn't able to fix it myself since it's a pretty low-level issue, so I think it's time to look for a workaround.
Angular allows you to put a promise (or anything with a .then(...) function) into your scope, and once it is resolved, all $watches and anything bound to that promise will use the resolved value. The issue arises when you use a function to return a promise, as the same doesn't apply - it's handled like a plain object.
For example:
var helloDef = $.Deferred();
$scope.hello = helloDef.promise();
$scope.getHello = function() {
return $scope.hello;
};
$timeout(function() {
helloDef.resolve('Hello!');
}, 1000);
Fiddle
Here using ng-bind="hello" works fine and outputs Hello!, but ng-bind="getHello()" outputs [object Object] as the internal $watch returns the promise object. Works the same way with $q instead of $.Deferred.
In my actual code I'm creating the promise the first time the function is called, so I can't just pre-make the promise and refer to that in scope.
I also need it for more than just ng-bind, so making my own binding directive which handles this case properly isn't viable.
Anyone have any ideas? I'm currently returning the promise if the data hasn't resolved and the actual result if it has, but that's a pain to work with. Anything bound to the data briefly causes weird side effects as the data is being loaded, like ngRepeat using the promise object instead of the resolved value to create elements.
Thanks.
UPDATE: Pull request: https://github.com/angular/angular.js/pull/3605
UPDATE 2: For future reference, automatic promise unwrapping has been deprecated in the 1.2 brach.
For some things, I use $resource. If I need to wait for it, $then works well:
var r = $resource...get...
var p = r.$then...
Otherwise, I build my own resource-like object that is not a promise, but has a promise that I can wait for.
Pull request merged, it should be fixed in 1.2.0 so I'll mark this as the answer.
I understand that typically one would just attach continuation code with a then() call and chain behaviour when using promises.
However, I want to kick off a promise-wrapped asynchronous call and then separately kick off a 3-second $timeout() so I can take a UI action, ONLY IF the original promise has not yet completed. (I anticipate that this would only happen on slow connections, mobile devices on 3G, etc.)
Given a promise, can I check whether it's complete or not without blocking or waiting?
I guess this was added in a recent version of Angular but there seems to be now an $$state object on the promise:
var deferred = $q.defer();
console.log(deferred.promise.$$state.status); // 0
deferred.resolve();
console.log(deferred.promise.$$state.status); //1
As noted in the comments this is not recommended as it might break when upgrading your Angular version.
I think your best option as is, (without modifying the Angular source and submitting a pull request) is to keep a local flag for if the promise has been resolved. Reset it every time you setup the promise you're interested in and mark it as complete in the then() for the original promise. In the $timeout then() check the flag to know if the original promise has resolved yet or not.
Something like this:
var promiseCompleted = false;
promise.then(function(){promiseCompleted=true;})
$timeout(...).then(function(){if(!promiseCompleted)doStuff()})
Kris Kowal's implementation includes other methods for checking the state of the promise but it appears Angular's implementation of $q unfortunately doesn't include these.
It doesn't seem to be possible, as #shaunhusain already mentioned. But maybe it's not necessary:
// shows stuff from 3s ahead to promise completetion,
// or does and undoes it in one step if promise completes before
$q.all(promise, $timeout(doStuff, 3000)).then(undoStuff);
or maybe better:
var tooSlow = $timeout(doStuff, 3000);
promise.always(tooSlow.cancel);
I have had a similar problem where I need to check if a promise has returned. Because AngularJS's $watch function will register a change while rendering the page even if both new and old values are undefined, I have to check to see if there is any data worth storing in my external model.
It is definitely a hack but I do this:
$scope.$watch('userSelection', function() {
if(promiseObject.hasOwnProperty("$$v"){
userExportableState.selection = $scope.userSelection;
}
};
I know that $$v is an internal variable used by AngularJS, but it has been quite reliable as an indicator of a resolved promise for us. Who knows what will happen when we upgrade to AngularJS 1.2 :-/ I don't see any mention of improvements to $q in the 1.2 docs but perhaps someone will write a replacement service with a better feature set closer to Q.
I don't know your exact scenario but it is more typical to put a timeout in place immediately after making the asynchronous call (and generating the promise).
Providing the setTimeout() statement is in the same event thread as the asynchronous call, you needn't worry about the possibility of a race effect. As javascript is strictly single threaded, the promise's .then() callbacks are guaranteed to fire in a later event thread.