Angular 2 how to keep event from triggering digest loop/detection cycle?
Solution 1:
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:
- https://angular.io/guide/browser-support#polyfills-for-non-cli-users
Solution 2:
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();
}