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

544 views Asked by At

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.

2

There are 2 answers

2
georgeawg On BEST ANSWER

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

0
Yoan On

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