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.
Related
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();
});
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.
So I am pretty stuck. I have used angular promises within controllers before similar to this:
thisCtrl.someFunction = function (data){
//...
}
ApiCall.get.something().then(function(data){
thisCtrl.newData = data;
thisCtrl.someFunction(thisCtrl.newData);
});
and I have been able to then use thisCtrl.newData (created within the promise) throughout the controller. I have also been able to use functions defined outside of the promise, within the promise - as shown in the above ex.
For some reason when trying to do this in a service I cant seem to make it work. Trying to use the same pattern as above:
this.someFunction = function (data){
//...
}
ApiCall.get.something().then(function(data){
this.newData = data; // Cannot read property of 'newData' of undefined
this.someFunction(thisCtrl.newData); //error: Cannot read property of 'someFunctions' of undefined
});
So it seems that once I do the same thing in the service, this, is out of scope - rendering me kind of helpless in passing that data around to the rest of the service, or using any data from the rest of the service inside the promise. I have tried returning the data as an object:
ApiCall.get.something().then(function(data){
return {
data: data
}
});
But it too returns a promise, leaving me in the same boat. I have actually tried a LOT of things, and googled quite a bit. I want this to be available in a service to prevent repeating large blocks of code in multiple controllers. Can I not create promises inside of services? If so, any help is appreciated as to how to properly do it.... Thanks!
When you use "this", it may not be what you expect because "this" depends on the function context.
To make sure you're binding to the right function context use "fn.bind(this)"
ApiCall.get.something().then(function(data){
this.newData = data;
this.someFunction(...);
}.bind(this));
Alternatively, for wider cross-browser compatibility, you can save "this" as "self" in the outer function scope, so that you can use it from your inner function:
var self = this;
ApiCall.get.something().then(function(data){
self.newData = data;
self.someFunction(...);
});
For more information, check out this reference: https://codereview.stackexchange.com/questions/49872/using-var-self-this-or-bindthis
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) {
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.