Any way to know if a variable is an angularjs promise? - angularjs

I'm making a directive that takes a function as a scope parameter (scope: { method:'&theFunction' }). I need to know if the result returned by that method is an angular promise (if yes something will happen on resolution, otherwise it happens right away).
For now I'm testing if foo.then exists but I was wondering if there was a better way to do it.

You can use $q.when to wrap the object as a promise (whether it is or not). Then, you can be sure that you are always dealing with a promise. This should simplify the code that then handles the result.
Documentation for $q.when is here with $q.

Angular's when() is a good option as Davin mentioned.
If that doesn't meet your needs then Angular's internal way of checking (it uses this inside when) is very close to what you're doing:
var ref = function(value) {
if (value && isFunction(value.then)) {
// Then this is promise
}

#kayakDave, thanks for guiding to right place.
angular $q
when(value, [successCallback], [errorCallback], [progressCallback]);
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
This is useful when you are dealing with an object that might or might not be a promise,
or if the promise comes from a source that can't be trusted.
$q.when(value).then(function (data) {
//this helps me to bind data from $resource or $http or object
}
check this fiddle

The $q.when() answer seems like the best answer for most use cases, I used instanceof for mine.
if(buttonData instanceof $q) {
buttonData.then(function(actions) {
$scope.buttonActions = actions;
});
} else {
$scope.button = buttonData;
}
Alternatively, the following IF worked as well, but I ended up going with the above solution.
if(Object.getPrototypeOf(buttonData) === $q.prototype) {

Related

How to use $timeout effectively in Angular?

I have the following code that doesn't work:
$scope.function_A();
$scope.function_B();
and this DOES work:
$scope.function_A();
$timeout(function(){
$scope.function_B();
}),100;
This is due to the fact that function_B refers to a directive that hasn't been created yet by Angular. I believe that's why using $timeout fixes the problem.
My issue is: how to be sure that the 100 millisecond timeout is correct and will always work? Is it better to somehow detect that function_A finished instead of using $timeout?
You can use Promises.
If you require function A to finish it's work, before calling function B, it is a good idea to make it return a promise. Some angular services have methods that already return a Promise, e.g. $http.get. By using $q you can prepare your own promises. For example:
$scope.function_A = function() {
//function code
return $q.when();
}
$scope.function_A().then(function() {
$scope.function_B();
});
Read more about $q and Promises here
Use the callback mechanism
$scope.function_A(callback){
// your implementation
callback() // add it at the end
}
Now call function_B inside function_A
$scope.function_A(function(){
$scope.function_B();
});

Return value from service once $resource promise is resolved

I have such working code:
Service (factory?):
myApp.factory('MyService', ['$q','$resource', 'MyResource',
function ($q, $resource, MyResource) {
return {
getRoles: function () {
return MyResource.getRoles(); //MyResource makes API calls using $resource
} } });
Controller:
$scope.Roles = MyService.getRoles();
$scope.Roles.$promise.then(function () { $scope.RolesCount = $scope.Roles.length; });
What I'd like to do is to create such function in MyService that will return number of roles once getRoles is resolved so I can use it in controller like this:
$scope.RolesCount = MyService.getRolesCount();
and then in html
{{RolesCount}}
Unfortunately, I have no idea how to do this since my getRolesCount() method needs to return something so I can't use $promise.then() inside MyService. I'll try to update my question's title once I come up with something more accurate.
If server response needs to be transformed in a service, then then should be moved there. However, it's not practical or possible if then is supposed to unwrap a promise and assign a value to scope, like in the code above.
$resource returns an object that is filled on promise resolution. The way a resource is supposed to be used in view is:
{{Roles.length}}
When an object is updated, it will be updated in view, too. Assigning the value to another variable is inefficient.
It's impossible to do something like
$scope.RolesCount = MyService.getRolesCount();
because getRolesCount is asynchronous, and the value it resolves with is scalar. It is possible to flatten it with `async..await, (TypeScript or ES2017), but will require additional measures to synchronize control flow with AngularJS digests, as explained in this answer.

In AngularJS, what is the best way to force a view update when the model changes?

This is a very common question, but I have never found the answer that works properly. I have come across three answers, but none always works.
$apply: This will force an update, but will randomly through an error if it gets called while a digest is in progress.
"safe apply" where there is a check for a digest in progress before calling $apply. This doesn't always update the view for some reason I haven't been able to determine. In addition, there is a small chance that a digest will start between the check and $apply.
$timeout: according to the documentation, this should work reliably but doesn't seem to always update the view.
from $timeout documentation, the parameter invokeApply:
If set to false skips model dirty checking, otherwise will invoke fn within the $apply block. (default: true)
It never throughs an error, but sometimes doesn't update the view during a page load.
Here is a code sample where the problem occurs during page initialization:
EditService.getEvents(Gparams.curPersonID)
.then(function successCallback(response) {
if (response.status=='200') {
do some stuff
} else {
handle an error
}
var timer = $timeout(function() { })
.then(function successCallback(response) {
do something
});
$scope.$on("$destroy", function(event {
$timeout.cancel(timer)});
}); });
What is the correct answer? Please don't just say what but also discuss why.
Here is a code sample where the problem occurs during page initialization
A common cause of .then methods not updating the DOM is that the promise is not an AngularJS $q service promise. The solution is to convert the suspect promise to a $q service promise with the $q.when method.
//EditService.getEvents(Gparams.curPersonID)
//CONVERT to $q service promise
$q.when(EditService.getEvents(Gparams.curPersonID))
.then(function successCallback(response) {
if (response.status=='200') {
do some stuff
} else {
handle an error
}
The .then method of a $q service promise is integrated with the AngularJS framework and its digest cycle. Changes to the scope model will automatically update the DOM.
when
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
--AngularJS $q Service API Reference - $q.when

How to return fully resolved promise?

I am trying to play around with $q, write some tests, try to stub promises etc. and I wondered if there is a way to return fully resolved promise like one can do it with whenjs, when("stuff to return), something that would be equal to this
function fullyResolvedPromise(expectedResponse) {
var dfd = $q.defer();
dfd.resolve(expectedResponse);
$rootScope.$apply();
return dfd.promise;
}
Clarification: I know this code works, but I want to do it without writing this function. I want to do something like this $q(expectedresponse) and get equivalent to above code. That is what I am after. Just like with whenjs you can write when(stuffToResolve) and it would return you a fully resolved promise.
After posting a clarification I re-read the documentation and there it is - when() method.
So I can use $q.when(stuffToResolve) and it would be equal to calling the above function.

AngularJS - binding/watching a function which returns a promise

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.

Resources