Angular - Выход из Zone.JS для определенных обратных вызовов - PullRequest
0 голосов
/ 24 октября 2019

Прежде всего, я хорошо осведомлен о zone.runOutsideAngular(callback), задокументированном здесь .

Что этот метод делает, выполняет обратный вызов в другой зонечем Angular.

Мне нужно прикрепить очень быстрый обратный вызов к событию document mousemove (для подсчета времени простоя). Я не хочу, чтобы весь механизм задач Zone.JS добавлялся в очереди выполнения для этого конкретного обратного вызова. Мне бы очень хотелось, чтобы обратные вызовы выполнялись в простом, непатентованном браузере.

Код регистрации события, который у меня есть:

  private registerIdleCallback(callback: (idleFromSeconds: number) => void) {
    let idleFromSeconds = 0;
    const resetCounter = function() {
      idleFromSeconds = -1;
    };
    document.addEventListener('mousemove', resetCounter);
    document.addEventListener('keypress', resetCounter);
    document.addEventListener('touchstart', resetCounter);
    window.setInterval(function() {
      callback(++idleFromSeconds);
    }, 1000);
  }

Вопрос в том, как я могу получить этокод для использования без исправлений document.addEventListener, обеспечивающий полное отделение от Zone.JS и истинную собственную производительность?

Ответы [ 2 ]

1 голос
/ 28 октября 2019

Ну, это оказалось простым, но не простым, так что я собираюсь опубликовать решение, которое я нашел здесь.

Что я хочу сделать, это сохранить полезное document.addEventListener в глобальномобъект до Zone.JS исправляет ад из нативных объектов браузера.

Это необходимо сделать до загрузки полифиллов, потому что именно там загружается Zone. И это не должно быть сделано в виде простого кода в polyfills.ts, потому что операторы import обрабатываются перед любым другим кодом.

Поэтому мне нужен файл zone-config.ts в той же папке polyfills.ts,В последнем необходим дополнительный импорт:

import './zone-config'; // <- adding this line to the file, before...
// Zone JS is required by default for Angular itself.
import 'zone.js/dist/zone'; // Included with Angular CLI.

В zone-config.ts я делаю ловкость рук:

(function(global) {
  // Save native handlers in the window.unpatched object.
  if (global.unpatched) return;

  if (global.Zone) {
    throw Error('Zone already running: cannot access native listeners');
  }

  global.unpatched = {
    windowAddEventListener: window.addEventListener.bind(window),
    windowRemoveEventListener: window.removeEventListener.bind(window),
    documentAddEventListener: document.addEventListener.bind(document),
    documentRemoveEventListener: document.removeEventListener.bind(document)
  };

  // Disable Zone.JS patching of WebSocket -- UNRELATED TO THIS QUESTION
  const propsArray = global.__Zone_ignore_on_properties || (global.__Zone_ignore_on_properties = []);
  propsArray.push({ target: WebSocket.prototype, ignoreProperties: ['close', 'error', 'open', 'message'] });

  // disable addEventListener
  const evtsArray = global.__zone_symbol__BLACK_LISTED_EVENTS || (global.__zone_symbol__BLACK_LISTED_EVENTS = []);
  evtsArray.push('message');
})(<any>window);

Теперь у меня есть объект window.unpatched,это позволяет мне полностью отказаться от Zone.JS для очень специфических задач, таких как IdleService, над которым я работал:

import { Injectable, NgZone } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter } from 'rxjs/operators';

/* This is the object initialized in zone-config.ts */
const unpatched: {
  windowAddEventListener: typeof window.addEventListener;
  windowRemoveEventListener: typeof window.removeEventListener;
  documentAddEventListener: typeof document.addEventListener;
  documentRemoveEventListener: typeof document.removeEventListener;
} = window['unpatched'];

/** Manages event handlers used to detect User activity on the page,
 * either via mouse or keyboard events. */
@Injectable({
  providedIn: 'root'
})
export class IdleService {
  private _idleForSecond$ = new BehaviorSubject<number>(0);

  constructor(zone: NgZone) {
    const timerCallback = (idleFromSeconds: number): void => this._idleForSecond$.next(idleFromSeconds);
    this.registerIdleCallback(timerCallback);
  }

  private registerIdleCallback(callback: (idleFromSeconds: number) => void) {
    let idleFromSeconds = 0;
    const resetCounter = () => (idleFromSeconds = -1);

    // runs entirely out of Zone.JS
    unpatched.documentAddEventListener('mousemove', resetCounter);
    unpatched.documentAddEventListener('keypress', resetCounter);
    unpatched.documentAddEventListener('touchstart', resetCounter);

    // runs in the Zone normally
    window.setInterval(() => callback(++idleFromSeconds), 1000);
  }

  /** Returns an observable that emits when the user's inactivity time
   * surpasses a certain threshold.
   *
   * @param seconds The minimum inactivity time in seconds.
   */
  byAtLeast(seconds: number): Observable<number> {
    return this._idleForSecond$.pipe(filter(idleTime => idleTime >= seconds));
  }

  /** Returns an observable that emits every second the number of seconds since the
   * ladt user's activity.
   */
  by(): Observable<number> {
    return this._idleForSecond$.asObservable();
  }
}

Теперь stackTrace в обратном вызове (idleFromSeconds = -1) пуст по желанию.

Надеюсь, что это может помочь кому-то еще: это не сложно, но иметь все биты на месте было немного хлопотно.

1 голос
/ 24 октября 2019

Я не уверен, получил ли я то, что вы хотите сделать. Я сделал небольшой плункер, который показывает, как сделать обратный вызов события извне Angular.

https://stackblitz.com/edit/angular-s3krnu

...