AngularJS 1.6 and non-Angular event handler

476 views Asked by At

Using AngularJS 1.6 and braintree-web 3.6.2 Hosted Fields. I've wrapped the Braintree callbacks in promises using $q (though new Promise() works fine, too). Right now, I'm simulating $scope.$apply() using $timeout with no time parameter.

Here's a snippet of ES6 code from the class for my service:

'use strict';
import braintree from 'braintree-web';

export class Cart {
  constructor($q, $log) {
    this.$q = $q;
    this.$log = $log;
  }

  // Handle the callback as a promise
  braintreeHostedFieldsCreate(clientInstance) {
    return this.$q((resolve, reject) => {
      braintree.hostedFields.create({
        client: clientInstance,
        fields: {
          number: {
            selector: '#card-number',
            placeholder: '4111 1111 1111 1111'
          },
          cvv: {
            selector: '#cvv',
            placeholder: '123'
          },
          expirationDate: {
            selector: '#expiration-date',
            placeholder: '10/2019'
          }
        },
        styles: {
          input: {
            'font-size': '14px',
            'font-family': 'Helvetica Neue, Helvetica, Arial, sans-serif',
            color: '#555'
          },
          ':focus': {
            'border-color': '#66afe9'
          },
          'input.invalid': {
            color: 'red'
          },
          'input.valid': {
            color: 'green'
          }
        }
      }, (hostedFieldsErr, hostedFieldsInstance) => {
        // Make event handlers run digest cycle using $timeout (simulate $scope.apply())
        hostedFieldsInstance.on('blur', event => this.$timeout(() => event));
        hostedFieldsInstance.on('focus', event => this.$timeout(() => event));
        hostedFieldsInstance.on('validityChange', event => this.$timeout(() => event));
        hostedFieldsInstance.on('empty', event => this.$timeout(() => event));
        hostedFieldsInstance.on('notEmpty', event => this.$timeout(() => event));

        // Reject or resolve the promise
        if(hostedFieldsErr) {
          this.$log.error('Not able to create the hosted fields with Braintree.', hostedFieldsErr);
          return reject(hostedFieldsErr);
        } else {
          this.hostedFieldsInstance = hostedFieldsInstance;
          return resolve(hostedFieldsInstance);
        }
      });
    });
  }

}

Is using $timeout in this situation a good substitute for $scope.$apply() as the latter's use after AngularJS 1.5 appears to be discouraged?

1

There are 1 answers

2
tasseKATT On BEST ANSWER

If an Angular service wraps code that uses for example DOM events, it should be the serviceĀ“s responsibility to make sure the digest loop is started (if needed).

Using $apply/$digest is still the correct way to do this.

The service can simply inject the $rootScope and wrap the event handler functionality in a call to $apply, keeping it blackboxed from the users.

An alternative is to demand the service user to pass a scope and instead call $digest, which would limit the amount of watchers processed. In my mind however the (most likely negligible) performance increase from this wouldn't be worth the added complexity.

While $timeout is also a possibility, it would just postpone the execution unnecessarily and in the end call $apply on the $rootScope anyway.