We are implementing drag and drop functionality with Angular 2.
I'm using the dragover
event just to run the preventDefault()
function. So that the drop
event works as explained in this question.
The dragover
method is being handled by the onDragOver
function in the component.
<div draggable="true"
(dragover)="onDragOver($event)">
...
In the component, this function prevents default behavior allowing for the dragged item to be dropped at this target.
onDragOver(event) {
event.preventDefault();
}
This works as expected. The dragover event gets fired every few hundred milliseconds.
But, every time the onDragOver
function is called, Angular 2 runs its digest cycle. This slows down the application. I'd like to run this function without triggering the digest cycle.
A workaround we use for this is subscribing to element event and running it outside of the Angular 2's context as follows:
constructor( ele: ElementRef, private ngZone: NgZone ) {
this.ngZone.runOutsideAngular( () => {
Observable.fromEvent(ele.nativeElement, "dragover")
.subscribe( (event: Event) => {
event.preventDefault();
}
);
});
}
This works fine. But is there a way to achieve this without having to access the nativeElement directly?
1) One interesting solution might be overriding EventManager
custom-event-manager.ts
import { Injectable, Inject, NgZone } from '@angular/core';
import { EVENT_MANAGER_PLUGINS, EventManager } from '@angular/platform-browser';
@Injectable()
export class CustomEventManager extends EventManager {
constructor(@Inject(EVENT_MANAGER_PLUGINS) plugins: any[], private zone: NgZone) {
super(plugins, zone);
}
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
if(eventName.endsWith('out-zone')) {
eventName = eventName.split('.')[0];
return this.zone.runOutsideAngular(() =>
super.addEventListener(element, eventName, handler));
}
return super.addEventListener(element, eventName, handler);
}
}
app.module.ts
...
providers: [
{ provide: EventManager, useClass: CustomEventManager }
]
})
export class AppModule {
Usage:
<h1 (click.out-zone)="test()">Click outside ng zone</h1>
<div (dragover.out-zone)="onDragOver($event)">
Plunker Example
So with solution above you can use one of these options to prevent default behavior and run event outside angular zone:
(dragover.out-zone)="$event.preventDefault()"
(dragover.out-zone)="false"
(dragover.out-zone)="!!0"
2) One more solution provided by Rob Wormald is using blacklist for Zonejs
blacklist.ts
/// <reference types='zone.js/dist/zone.js' />
const BLACKLISTED_ZONE_EVENTS: string[] = [
'addEventListener:mouseenter',
'addEventListener:mouseleave',
'addEventListener:mousemove',
'addEventListener:mouseout',
'addEventListener:mouseover',
'addEventListener:mousewheel',
'addEventListener:scroll',
'requestAnimationFrame',
];
export const blacklistZone = Zone.current.fork({
name: 'blacklist',
onScheduleTask: (delegate: ZoneDelegate, current: Zone, target: Zone,
task: Task): Task => {
// Blacklist scroll, mouse, and request animation frame events.
if (task.type === 'eventTask' &&
BLACKLISTED_ZONE_EVENTS.some(
(name) => task.source.indexOf(name) > -1)) {
task.cancelScheduleRequest();
// Schedule task in root zone, note Zone.root != target,
// "target" Zone is Angular. Scheduling a task within Zone.root will
// prevent the infinite digest cycle from appearing.
return Zone.root.scheduleTask(task);
} else {
return delegate.scheduleTask(target, task);
}
}
});
main.ts
import {blacklistZone} from './blacklist'
blacklistZone.run(() => {
platformBrowser().bootstrapModuleFactory(...)
})
Plunker with blacklist
Update:
5.0.0-beta.7 (2017-09-13)
fix(platform-browser): run BLACK_LISTED_EVENTS outside of ngZone
Follow
https://github.com/angular/angular/issues/19989
https://github.com/angular/angular/commit/e82812b9a9992bc275bba54575f9d780753c1f8f
https://github.com/angular/angular/pull/21681
Update 2
Angular cli includes template for disabling parts of macroTask/DomEvents patch.
Just open
polyfills.ts
You can find there the following code
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
https://github.com/angular/devkit/blob/8651a94380eccef0e77b509ee9d2fff4030fbfc2/packages/schematics/angular/application/files/sourcedir/polyfills.ts#L55-L68
See also:
You can detach the change detector to prevent change detection to be invoked for a component
constructor(private cdRef:ChangeDetectorRef) {}
foo() {
this.cdRef.detach();
...
this.cdRef.attach();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With