Angular 2 如何防止事件触发摘要循环/检测循环? [英] Angular 2 how to keep event from triggering digest loop/detection cycle?

查看:27
本文介绍了Angular 2 如何防止事件触发摘要循环/检测循环?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我使用 dragover 事件只是为了运行 preventDefault() 函数.以便 drop 事件按照 this question 中的说明工作.

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

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

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

这按预期工作.dragover 事件每几百毫秒触发一次.

但是,每次调用 onDragOver 函数时,Angular 2 都会运行它的摘要循环.这会减慢应用程序的速度.我想在不触发摘要循环的情况下运行此功能.

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

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

这很好用.但是有没有办法实现这一点而不必直接访问 nativeElement ?

解决方案

1) 一个有趣的解决方案可能是覆盖 EventManager

custom-event-manager.ts

import { Injectable, Inject, NgZone } from '@angular/core';从@angular/platform-b​​rowser"导入 { EVENT_MANAGER_PLUGINS, EventManager };@Injectable()导出类 CustomEventManager 扩展 EventManager {构造函数(@Inject(EVENT_MANAGER_PLUGINS)插件:任何[],私有区域:NgZone){超级(插件,区域);}addEventListener(元素:HTMLElement,事件名称:字符串,处理程序:函数):函数{if(eventName.endsWith('out-zone')) {eventName = eventName.split('.')[0];返回 this.zone.runOutsideAngular(() =>super.addEventListener(element, eventName, handler));}return super.addEventListener(element, eventName, handler);}}

app.module.ts

 ...提供者:[{ 提供:EventManager,useClass:CustomEventManager }]})导出类 AppModule {

用法:

<h1 (click.out-zone)="test()">点击ng区外</h1><div (dragover.out-zone)="onDragOver($event)">

Plunker 示例

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

(dragover.out-zone)="$event.preventDefault()"(dragover.out-zone)="假"(dragover.out-zone)="!!0"

2) Rob Wormald 提供的另一种解决方案是对 Zonejs 使用黑名单

blacklist.ts

///<reference types='zone.js/dist/zone.js'/>常量 BLACKLISTED_ZONE_EVENTS:字符串 [] = ['addEventListener:mouseenter','addEventListener:mouseleave','addEventListener:mousemove','addEventListener:mouseout','addEventListener:mouseover','addEventListener:mousewheel','addEventListener:scroll','requestAnimationFrame',];出口 const blacklistZone = Zone.current.fork({名称:'黑名单',onScheduleTask: (delegate: ZoneDelegate, current: Zone, target: Zone,任务:任务):任务=>{//黑名单滚动、鼠标和请求动画帧事件.if (task.type === 'eventTask' &&BLACKLISTED_ZONE_EVENTS.some((名称)=>task.source.indexOf(name) >-1)) {task.cancelScheduleRequest();//在根区调度任务,注意 Zone.root != target,//目标"区域是 Angular.在 Zone.root 中安排任务将//防止出现无限摘要循环.返回 Zone.root.scheduleTask(task);} 别的 {return delegate.scheduleTask(target, task);}}});

ma​​in.ts

import {blacklistZone} from './blacklist'blacklistZone.run(() => {platformBrowser().bootstrapModuleFactory(...)})

带有黑名单的插件

更新:

5.0.0-beta.7 (2017-09-13)

修复(平台浏览器):在 ngZone 之外运行 BLACK_LISTED_EVENTS

关注

更新 2

Angular cli 包含用于禁用部分宏任务/DomEvents 补丁的模板.

直接打开

polyfills.ts

你可以在那里找到以下代码

/*** 默认情况下,zone.js 将修补所有可能的宏任务和 DomEvents* 用户可以通过设置以下标志来禁用部分宏任务/DomEvents 补丁*///(window as any).__Zone_disable_requestAnimationFrame = true;//禁用补丁 requestAnimationFrame//(任何窗口).__Zone_disable_on_property = true;//禁用补丁 onProperty 例如 onclick//(window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove'];//禁用补丁指定的事件名称/** 在 IE/Edge 开发者工具中,addEventListener 也会被 zone.js 包裹* 使用以下标志,它将绕过 IE/Edge 的 `zone.js` 补丁*///(任何窗口).__Zone_enable_cross_context_check = true;

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

另见:

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

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:

这篇关于Angular 2 如何防止事件触发摘要循环/检测循环?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆