nstuyvesant
nstuyvesant

Reputation: 1516

AngularJS 1.6 and non-Angular event handler

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?

Upvotes: 0

Views: 456

Answers (1)

tasseKATT
tasseKATT

Reputation: 38490

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.

Upvotes: 1

Related Questions