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);