Delayed $scope updates in promise `.then` method - angularjs

I'm using angularJS and I do this:
xxx.then(function (response) {
$scope.x = response.x;
$scope.y = response.y;
}, function (error) {}
);
The response come from server not instantantly. Then when the response come, I want than the scope update my value, but it does that just when I click in some button other so.
In my html I receive the informations so:
<p>{{x}}</p>
<p>{{y}}</p>
Do you know what I'm doing wrong?

This could be an issue with the digest cycle, try doing $scope.$apply() like below :
xxx.then(function (response) {
$scope.x = response.x;
$scope.y = response.y;
$scope.$apply();
}, function (error) {});
In AngularJS the results of promise resolution are propagated
asynchronously, inside a $digest cycle. So, callbacks registered with
then() will only be called upon entering a $digest cycle.
The results of your promise will not be propagated until the next digest cycle. As there is nothing else in your code that triggers the digest cycle, changes are not getting applied immediately. But, when you click on a button , it triggers the digest cycle, due to which the changes are getting applied
Check this for a clear explanation about this.

A .then method may fail to update the DOM if the promise comes from a source other than the $q service. Use $q.when to convert the external promise to a $q service promise:
//xxx.then(function (response) {
$q.when(xxx).then(function (data) {
$scope.x = data.x;
$scope.y = data.y;
}, function (error) {
throw error;
});
The $q service is integrated with the AngularJS framework and the $q service .then method will automatically invoke the framework digest cycle to update the DOM.
$q.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

Related

AngularJS digest cycle not firing during $http.post.then()

Using AngularJS 1.6.1 (ES6/Babel)... controller invokes a service method which uses $http.post() so I thought it would automatically run the digest. Had to add $timeout to force it (prefer to avoid $scope.$apply as this is an Angular component and will be upgrading to AngularJS 2 soon).
Is there a better approach than what I've done? Should then() off of what was originally an $http.post have run the digest cycle? If I don't include $timeout, nothing gets updated in the view.
Place Order button submits a form with its ngClick as $ctrl.placeOrder(checkout):
placeOrder(form) {
if(form.$valid) {
return this.Cart.placeOrder()
.then(saleResponse => {
// Page has {{ $ctrl.pageName }} but won't update without digest cycle
this.pageName = 'Order Confirmation'; // displays confirmation
form.$setPristine(); // treat the fields as untouched
form.$submitted = false; // reset submitted state
// Force a digest to run. Why is this needed?
this.$timeout(() => this.pageName);
})
.catch(saleResponse => {
form.$submitted = false;
this.errorMessage = saleResponse.message;
if(this.errorMessage.includes('card')) this.focusOnCardNumber();
});
}
}
Here's Cart.placeOrder():
placeOrder() {
// braintreeHostedFieldsTokenize() is a wrapper to return new Promise for their callback-based API
return this.braintreeHostedFieldsTokenize(this.hostedFieldsInstance)
.then(this.postOrderInformation.bind(this));
}
and Cart.postOrderInformation()
postOrderInformation(payload) {
const orderInformation = {
nonceFromClient: payload.nonce,
purchaser: this.purchaser,
recipient: this.recipient,
cartItems: this.cartItems
};
return this.$http
.post('/api/order', orderInformation)
.then(orderResponse => {
this.confirmation = orderResponse.data;
if(!orderResponse.data.success) throw orderResponse.data;
return this.confirmation;
});
}
Any thoughts about where I might have gone wrong necessitating the use of $timeout? My assumption was that $http.post's then() would run the digest cycle on its own since it's AngularJS. Thanks in advance.
My thought is that the .then method of Cart.placeOrder() is being performed on a promise library/queue that is external to the Angular $q Service library/queue. Try moving it to the $q Service with $q.when:
placeOrder(form) {
if(form.$valid) {
//return this.Cart.placeOrder()
var promise = this.Cart.placeOrder();
return $q.when(promise)
.then(saleResponse => {
// Page has {{ $ctrl.pageName }} but won't update without digest cycle
this.pageName = 'Order Confirmation'; // displays confirmation
form.$setPristine(); // treat the fields as untouched
form.$submitted = false; // reset submitted state
// Force a digest to run. Why is this needed?
// this.$timeout(() => this.pageName);
})
The promise returned by braintreeHostedFieldsTokenize() is not a $q Service promise and thus not integrated with the Angular digest cycle.
$q.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
I advice to use $evalAsync instead of $timeout. Check the documentation (https://docs.angularjs.org/api/ng/type/$rootScope.Scope) and this link: AngularJS : $evalAsync vs $timeout

Using the $q service with angular

I still can't understand the role of using the $q service, (what exactly will it add) if you want to create a service that need to call only one API via http , in this situation I don't know why shouldn't I just do the following (without using $q) :
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie)
.then(
function(response) {
return {
title: response.data.title,
cost: response.data.price
});
},
function(httpError) {
// translate the error
throw httpError.status + " : " +
httpError.data;
});
};
Very good question and one very few people appreciate the answer to.
Consider this:
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie);
};
Great code but these rules apply:
$http will resolve for 2xx responses and will otherwise reject. sometimes we don't want this. we want to reject on resolution and resolve on rejection. This makes sense when you consider a HEAD request to check the existence of something.
A HEAD request to /book/fred returning 200 shows book fred exists. But if my function is testing whether book fred is unique, it is not and so we want to reject on a 200. this is where $q comes in. Now I can do this:
var defer = $q.defer();
$http.head('/book/fred').then(function(response) {
// 2xx response so reject because its not unique
defer.reject(response);
}).catch(function(failResponse) {
defer.resolve(failResponse);
});
return defer.promise;
$q gives me total control of when I reject AND when I resolve.
Also, $q allows me to reject or resolve with any value. So if I am only interested in some of the response I can resolve with just the bit I am interested in.
Finally, I can use $q to turn non-promise based code into a promise.
var a = 5;
var b = 10;
var defer = $q.defer();
var resolve(a+b);
return defer.promise;
Bosh, if I need a promise as my return value then I've got one.
This is also great when mocking for unit tests.
AngularJS services such as $http, $timeout, $resource, etc. use the $q service internally to generate their promises. With those services there generally is no need to inject the $q service. In fact if you see $q.defer being used with those services, you should be asking Is this a “Deferred Antipattern”?.
There are some methods of the $q service that are useful in certain circumstances.
The $q.all method is used to wait on several promises.
var promise1 = $http.get(url1);
var promise2 = $http.get(url2);
$q.all([promise1, promise2]).then( responseArray ) {
$scope.data1 = responseArray[0].data;
$scope.data2 = responseArray[1].data;
}).catch( function ( firstError ) {
console.log(firstError.status)
});
The .catch method can be used to convert a rejected promise to a fulfilled promise. And vice versa with the .then method. No need to use $q.defer for that. For more information, see Angular execution order with $q.
The $q.when method is useful for generating a promise from unknown sources.
var promise = $q.when(ambiguousAPI(arg1));
The $q.when method creates a $q promise in all cases whether ambiguousAPI returns a value, a $q promise, or a promise from another library.
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
To summarize: The $q service is used to create a promise, so when using service (like $http,$timeout,$resource,etc.) that already return promises you generally don't need to use the $q service.
In this case you certainly don't need it because $http.get itself returns a promise. But for example if you perform async call only on some condition it is useful
function acyncService () {
if (dataLoaded) return $q.resolve(data);
return $http.get('path/to/load/data');
}
In this case even if you do not perform async call, you still can use
acyncService().then(function(data){
console.log(data);
});
This is only one of many examples. It is also useful to use $q promises when you do async requests with other libs like AWS SDK for instance.
$http does an asynchronous call. So, ideally if you want to get the value of the response in the url, you should use a '$scope' variable to store it.
$http("your url").then(function(response){
//This is the success callback
$scope.movies = response.data;
});

confused about the need for $scope.$apply

I have an angular controller:
.controller('DashCtrl', function($scope, Auth) {
$scope.login = function() {
Auth.login().then(function(result) {
$scope.userInfo = result;
});
};
});
Which is using a service I created:
.service('Auth', function($window) {
var authContext = $window.Microsoft.ADAL.AuthenticationContext(...);
this.login = function() {
return authContext.acquireTokenAsync(...)
.then(function(authResult) {
return authResult.userInfo;
});
};
});
The Auth service is using a Cordova plugin which would be outside of the angular world. I guess I am not clear when you need to use a $scope.$apply to update your $scope and when you don't. My incorrect assumption was since I had wrapped the logic into an angular service then I wouldn't need it in this instance, but nothing gets updated unless I wrap the $scope.userInfo = statement in a $timeout or $scope.$apply.
Why is it necessary in this case?
From angular's wiki:
AngularJS provides wrappers for common native JS async behaviors:
...
jQuery.ajax() => $http
This is just a traditional async function with a $scope.$apply()
called at the end, to tell AngularJS that an asynchronous event just
occurred.
So i guess since your Auth service does not use angular's $http, $scope.$apply() isn't called by angular after executing the Async Auth function.
Whenever possible, use AngularJS services instead of native. If you're
creating an AngularJS service (such as for sockets) it should have a
$scope.$apply() anywhere it fires a callback.
EDIT:
In your case, you should trigger the digest cycle once the model is updated by wrapping (as you did):
Auth.login().then(function(result) {
$scope.$apply(function(){
$scope.userInfo = result;
});
});
Or
Auth.login().then(function(result) {
$scope.userInfo = result;
$scope.$apply();
});
Angular does not know that $scope.userInfo was modified, so the digest cycle needs to be executed via the use of $scope.$apply to apply the changes to $scope.
Yes, $timeout will also trigger the digest cycle. It is simply the Angular version of setTimeout that will execute $scope.$apply after the wrapped code has been run.
In your case, $scope.$apply() would suffice.
NB: $timeout also has exception handling and returns a promise.

How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?

It seems that promises do not resolve in Angular/Jasmine tests unless you force a $scope.$digest(). This is silly IMO but fine, I have that working where applicable (controllers).
The situation I'm in now is I have a service which could care less about any scopes in the application, all it does it return some data from the server but the promise doesn't seem to be resolving.
app.service('myService', function($q) {
return {
getSomething: function() {
var deferred = $q.defer();
deferred.resolve('test');
return deferred.promise;
}
}
});
describe('Method: getSomething', function() {
// In this case the expect()s are never executed
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
});
done();
});
// This throws an error because done() is never called.
// Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
done();
});
});
});
What is the correct way to test this functionality?
Edit: Solution for reference. Apparently you are forced to inject and digest the $rootScope even if the service is not using it.
it('should get something', function($rootScope, done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
});
$rootScope.$digest();
done();
});
You need to inject $rootScope in your test and trigger $digest on it.
there is always the $rootScope, use it
inject(function($rootScope){
myRootScope=$rootScope;
})
....
myRootScope.$digest();
So I have be struggling with this all afternoon. After reading this post, I too felt that there was something off with the answer;it turns out there is. None of the above answers give a clear explanation as to where and why to use $rootScope.$digest. So, here is what I came up with.
First off why? You need to use $rootScope.$digest whenever you are responding from a non-angular event or callback. This would include pure DOM events, jQuery events, and other 3rd party Promise libraries other than $q which is part of angular.
Secondly where? In your code, NOT your test. There is no need to inject $rootScope into your test, it is only needed in your actual angular service. That is where all of the above fail to make clear what the answer is, they show $rootScope.$digest as being called from the test.
I hope this helps the next person that comes a long that has is same issue.
Update
I deleted this post yesterday when it got voted down. Today I continued to have this problem trying to use the answers, graciously provided above. So, I standby my answer at the cost of reputation points, and as such , I am undeleting it.
This is what you need in event handlers that are non-angular, and you are using $q and trying to test with Jasmine.
something.on('ready', function(err) {
$rootScope.$apply(function(){deferred.resolve()});
});
Note that it may need to be wrapped in a $timeout in some case.
something.on('ready', function(err) {
$timeout(function(){
$rootScope.$apply(function(){deferred.resolve()});
});
});
One more note. In the original problem examples you are calling done at the wrong time. You need to call done inside of the then method (or the catch or finally), of the promise, after is resolves. You are calling it before the promise resolves, which is causing the it clause to terminate.
From the angular documentation.
https://docs.angularjs.org/api/ng/service/$q
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));

AngularJS promise chain

I have my application that should open a popup ask a confirmation at the user, then make an ajax cal and close the popup.
I tried to do it using a chain of promise (I've already used it, and I remember that it should work in this way), but it seems to block after the call toreservationService.confirm($scope.object);. Now it is a fake service implemented with a setTimeout and $q just to return a promise (in future it will make the ajax call). Is this a valid code or I didn't undestood how promise works?
For the popup I choose AngularUI and the code is this:
reservationService.book($scope.object, day)
.then(function(){
var dialogOpts = {/* dialog options omitted*/}
return $dialog.dialog(dialogOpts).open();
})
.then(function(result){
console.log('confirmed? ', result);
if (result){
//After this line it doesn't do nothing, also if the promise is resolved
return reservationService.confirm($scope.object);
}
})
.then(function(){
//this function is never executed
$scope.$emit('object:detail',{object: $scope.object});
});
reservationService:
function confirm(){
var deferred = $q.defer();
setTimeout(function(){
console.log('Confirming');
deferred.resolve(true)
}, 500);
return deferred.promise;
}
SOLVED
change setTimeout with $timeout angular's service
Used $timeout instead of setTimeout 'cause it works togheter at the angular scope, forcing the digest phase (or use $scope.apply() inside the setTimeout).
Can you try
//skipping the first then
.then(function(result){
var deferred = $q.defer();
console.log('confirmed? ', result);
if (result){
//After this line it doesn't do nothing, also if the promise is resolved
return deferred.resolve(reservationService.confirm($scope.object));
}
deferred.resolve();
return deferred.promise;
})
.then(function(){
//this function is never executed
$scope.$emit('object:detail',{object: $scope.object});
});
For chaining then, the last then success or failure function should return a promise. As the $q documentation mentions
then(successCallback, errorCallback) – regardless of when the promise
was or will be resolved or rejected, then calls one of the success or
error callbacks asynchronously as soon as the result is available. The
callbacks are called with a single argument: the result or rejection
reason.
This method returns a new promise which is resolved or rejected via
the return value of the successCallback or errorCallback.

Resources