Angular 2如何防止事件触发摘要循环/检测周期?

时间:2017-03-30 03:44:05

标签: angular event-handling mouseevent

我们正在使用Angular 2实现拖放功能。

我正在使用dragover事件来运行preventDefault()函数。因此,drop事件的工作方式与this question中所述相同。

dragover方法由组件中的onDragOver函数处理。

<div draggable="true"
    (dragover)="onDragOver($event)">
...

在组件中,此功能可防止默认行为,允许将拖动的项目放在此目标上。

onDragOver(event) {
    event.preventDefault();
}

这可以按预期工作。每隔几百毫秒就会触发一次dragover事件。

但是,每次调用onDragOver函数时,Angular 2都会运行其摘要周期。这会降低应用程序的速度。我想在不触发摘要周期的情况下运行此功能。

我们使用的解决方法是订阅元素事件并在Angular 2的上下文之外运行它,如下所示:

constructor( ele: ElementRef, private ngZone: NgZone ) {
    this.ngZone.runOutsideAngular( () => {
        Observable.fromEvent(ele.nativeElement, "dragover")
            .subscribe( (event: Event) => {
                event.preventDefault();
            }
        );
    });
}

这很好用。但有没有办法实现这一点,而无需直接访问nativeElement?

2 个答案:

答案 0 :(得分:19)

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

<强> Plunker Example

因此,使用上述解决方案,您可以使用以下选项之一来防止默认行为并在角度区域外运行事件:

(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(...)
})

<强> Plunker with blacklist

<强>更新

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;

https://github.com/angular/devkit/blob/8651a94380eccef0e77b509ee9d2fff4030fbfc2/packages/schematics/angular/application/files/sourcedir/polyfills.ts#L55-L68

另见:

答案 1 :(得分:9)

您可以分离更改检测器以防止为组件调用更改检测

constructor(private cdRef:ChangeDetectorRef) {}
foo() {
  this.cdRef.detach();
  ...
  this.cdRef.attach();
}