angular 4, zone.js and a custom event of a javascript library

926 views Asked by At

I included cropper.js in my angular 4 project. in the component I use cropper.js I registered its ready event like so:

this.cropperOptions = {
    // omitted options which are not importent
    cropend: () => {
        this.changedOrTouched();
    },
    ready: () => {
        URL.revokeObjectURL(this.cropperImage.nativeElement.src);
        this.photoReady.emit();
        this.changedOrTouched();
    }
};

the emited ready event is consumed by the parent component which itself notifies a services

parent-component

photoReady(): void {
        this.loaderService.setLoaderStatus(false);
    }

service

export class LoaderService {
    public loaderStatus: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

    /**
     * indicates wheter something in the application is loading or not. Internal it calls next() on the loaderStatus BehaviorSubject.
     * This triggers any subscription to that BehaviorSubject which then can act.
     * @param value true if application is loading, false if not.
     */
    setLoaderStatus(value: boolean) {
        this.loaderStatus.next(value);
    }
}

a very simple services to which app.component subscribes like so

app.component

this.loaderService.loaderStatus.subscribe((val: boolean) => {
            this.isLoading = val;
            //this is here because of iOS
            this.changeDetector.detectChanges();
        });

what it does is show or hide a spinner.

Almost every browser works as expected, execpt IE10 which won't hide the spinner when it has been triggered by cropper.js ready event.

While zone.js still is a mystery to me sometimes I do (kinda) understand how it works.

I already had similar problems and which I (or other people) could usually fix by either using ChangeDetectorRef.detectChanges(), NgZone.run() ApplicationRef.tick, or wrapping calls in setTimeout.

Already tried those at various places to no avail and I don't understand why none of the above fixed the problem. Maybe cropper.js adds events in a way zone.js can't handle? Maybe there is a way to monkey patch custom events from external js libraries?

Any ideas?

Here's how cropper.js adds events cropper.js - add event

function addListener(element, type, _handler, once) {
  var types = trim(type).split(REGEXP_SPACES);
  var originalHandler = _handler;

  if (types.length > 1) {
    each(types, function (t) {
      addListener(element, t, _handler);
    });
    return;
  }

  if (once) {
    _handler = function handler() {
      for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
        args[_key4] = arguments[_key4];
      }

      removeListener(element, type, _handler);

      return originalHandler.apply(element, args);
    };
  }

  if (element.addEventListener) {
    element.addEventListener(type, _handler, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + type, _handler);
  }
}

cropper.js - dispatch events

function dispatchEvent(element, type, data) {
  if (element.dispatchEvent) {
    var event = void 0;

    // Event and CustomEvent on IE9-11 are global objects, not constructors
    if (isFunction(Event) && isFunction(CustomEvent)) {
      if (isUndefined(data)) {
        event = new Event(type, {
          bubbles: true,
          cancelable: true
        });
      } else {
        event = new CustomEvent(type, {
          detail: data,
          bubbles: true,
          cancelable: true
        });
      }
    } else if (isUndefined(data)) {
      event = document.createEvent('Event');
      event.initEvent(type, true, true);
    } else {
      event = document.createEvent('CustomEvent');
      event.initCustomEvent(type, true, true, data);
    }

    // IE9+
    return element.dispatchEvent(event);
  } else if (element.fireEvent) {
    // IE6-10 (native events only)
    return element.fireEvent('on' + type);
  }

  return true;
}

cropper.js - call ready event

// Call the "ready" option asynchronously to keep "image.cropper" is defined
      self.completing = setTimeout(function () {
        if (isFunction(options.ready)) {
          addListener(element, EVENT_READY, options.ready, true);
        }

        dispatchEvent(element, EVENT_READY);
        dispatchEvent(element, EVENT_CROP, self.getData());

        self.complete = true;
      }, 0);
1

There are 1 answers

1
jiali passion On BEST ANSWER

the reason that cropper.js not work with zone.js is cropper.js addEventListener/removeEventListenernot patched byzone.js, so it is not inngZone`, you can patch it yourself.

  1. Update zone.js to the latest version 0.8.18
  2. Add the following code
Zone.__load_patch("cropper", function(global, Zone, api) {
  // check cropper loaded or not
  if (!global["cropper"]) {
    return;
  }

  api.patchEventTarget(global, [global["cropper"].prototype], {
    addEventListenerFnName: "addListener",
    removeEventListenerFnName: "removeListener"
  });
});

NOTE: the code assumes cropper is a global object, if it is not, please get the cropper Type and replace the global['cropper'] part.