Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
309 views
in Technique[技术] by (71.8m points)

Angular 2 how to keep event from triggering digest loop/detection cycle?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...