Angular 2 如何防止事件触发摘要loop/detection 循环?
Angular 2 how to keep event from triggering digest loop/detection cycle?
我们正在使用 Angular 2 实现拖放功能。
我正在使用 dragover
事件来 运行 preventDefault()
函数。这样 drop
事件就如 this question 中所解释的那样工作。
组件中的 onDragOver
函数正在处理 dragover
方法。
<div draggable="true"
(dragover)="onDragOver($event)">
...
在组件中,此函数可防止默认行为允许将拖动的项目放在此目标上。
onDragOver(event) {
event.preventDefault();
}
这按预期工作。 dragover 事件每隔几百毫秒就会触发一次。
但是,每次 onDragOver
函数被调用时,Angular 2 运行s 它的摘要周期。这会减慢应用程序。我想 运行 这个函数而不触发摘要循环。
我们为此使用的解决方法是订阅元素事件,然后 运行 在 Angular 2 的上下文之外对其进行如下操作:
constructor( ele: ElementRef, private ngZone: NgZone ) {
this.ngZone.runOutsideAngular( () => {
Observable.fromEvent(ele.nativeElement, "dragover")
.subscribe( (event: Event) => {
event.preventDefault();
}
);
});
}
这很好用。但是有没有一种方法可以在不必直接访问 nativeElement 的情况下实现这一点?
您可以分离更改检测器以防止为组件调用更改检测
constructor(private cdRef:ChangeDetectorRef) {}
foo() {
this.cdRef.detach();
...
this.cdRef.attach();
}
1) 一个有趣的解决方案可能是覆盖 EventManager
自定义事件-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 {
用法:
<h1 (click.out-zone)="test()">Click outside ng zone</h1>
<div (dragover.out-zone)="onDragOver($event)">
因此,通过上述解决方案,您可以使用这些选项之一来防止默认行为和 运行 事件在 angular 区域之外:
(dragover.out-zone)="$event.preventDefault()"
(dragover.out-zone)="false"
(dragover.out-zone)="!!0"
2) Rob Wormald 提供的另一种解决方案是 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(...)
})
更新:
5.0.0-beta.7 (2017-09-13)
fix(platform-browser): run BLACK_LISTED_EVENTS outside of ngZone
关注
更新 2
Angular cli 包含用于禁用部分 macroTask/DomEvents 补丁的模板。
刚打开
polyfills.ts
您可以在那里找到以下代码
/**
* 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;
另请参阅:
我们正在使用 Angular 2 实现拖放功能。
我正在使用 dragover
事件来 运行 preventDefault()
函数。这样 drop
事件就如 this question 中所解释的那样工作。
组件中的 onDragOver
函数正在处理 dragover
方法。
<div draggable="true"
(dragover)="onDragOver($event)">
...
在组件中,此函数可防止默认行为允许将拖动的项目放在此目标上。
onDragOver(event) {
event.preventDefault();
}
这按预期工作。 dragover 事件每隔几百毫秒就会触发一次。
但是,每次 onDragOver
函数被调用时,Angular 2 运行s 它的摘要周期。这会减慢应用程序。我想 运行 这个函数而不触发摘要循环。
我们为此使用的解决方法是订阅元素事件,然后 运行 在 Angular 2 的上下文之外对其进行如下操作:
constructor( ele: ElementRef, private ngZone: NgZone ) {
this.ngZone.runOutsideAngular( () => {
Observable.fromEvent(ele.nativeElement, "dragover")
.subscribe( (event: Event) => {
event.preventDefault();
}
);
});
}
这很好用。但是有没有一种方法可以在不必直接访问 nativeElement 的情况下实现这一点?
您可以分离更改检测器以防止为组件调用更改检测
constructor(private cdRef:ChangeDetectorRef) {}
foo() {
this.cdRef.detach();
...
this.cdRef.attach();
}
1) 一个有趣的解决方案可能是覆盖 EventManager
自定义事件-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 {
用法:
<h1 (click.out-zone)="test()">Click outside ng zone</h1>
<div (dragover.out-zone)="onDragOver($event)">
因此,通过上述解决方案,您可以使用这些选项之一来防止默认行为和 运行 事件在 angular 区域之外:
(dragover.out-zone)="$event.preventDefault()"
(dragover.out-zone)="false"
(dragover.out-zone)="!!0"
2) Rob Wormald 提供的另一种解决方案是 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(...)
})
更新:
5.0.0-beta.7 (2017-09-13)
fix(platform-browser): run BLACK_LISTED_EVENTS outside of ngZone
关注
更新 2
Angular cli 包含用于禁用部分 macroTask/DomEvents 补丁的模板。
刚打开
polyfills.ts
您可以在那里找到以下代码
/**
* 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;
另请参阅: