Angular-退出Zone.JS进行特定的回调 [英] Angular - Getting out of Zone.JS for specific callbacks

查看:97
本文介绍了Angular-退出Zone.JS进行特定的回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我很了解 zone.runOutsideAngular(callback),该文档记录在此处.

此方法的作用是在与Angular区域不同的 区域中运行回调.

我需要在 document mousemove 事件中添加一个非常快速的回调(以计算空闲时间).我不想将整个Zone.JS任务处理机制附加到此特定回调的执行队列中.我真的很想让回调函数在无补丁的浏览器运行时中运行.

我拥有的事件注册代码是:

 <代码>私人registerIdleCallback(callback:(idleFromSeconds:number)=> void){让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的完全分离以及真正的本机性能?

解决方案

好吧,事实证明这很简单,但并不简单,因此我将发布在这里找到的解决方案.

我想做的是在Zone.JS之前将可用的 document.addEventListener 保存在全局对象中.>

这必须在加载polyfill之前完成,因为在该区域中已经加载了Zone.而且,决不能用 polyfills.ts 中的纯代码完成此操作,因为 import 语句是在处理其他任何代码之前进行的.

所以我需要在 polyfills.ts 的同一文件夹中的文件 zone-config.ts .在后者中,需要额外导入:

  import'./zone-config';//<-在...之前将此行添加到文件中//默认情况下,Angular本身需要Zone JS.导入'zone.js/dist/zone';//包含在Angular CLI中. 

zone-config.ts 中,我做惯了:

 (function(global){//将本机处理程序保存在window.unpatched对象中.如果(global.unpatched)返回;如果(global.Zone){抛出错误(区域已在运行:无法访问本机侦听器");}global.unpatched = {windowAddEventListener:window.addEventListener.bind(window),windowRemoveEventListener:window.removeEventListener.bind(window),documentAddEventListener:document.addEventListener.bind(document),documentRemoveEventListener:document.removeEventListener.bind(document)};//禁用WebSocket的Zone.JS补丁-与本问题无关const propsArray = global .__ Zone_ignore_on_properties ||(global .__ Zone_ignore_on_properties = []);propsArray.push({target:WebSocket.prototype,ignoreProperties:['close','error','open','message']});//禁用addEventListenerconst evtsArray = global .__ zone_symbol__BLACK_LISTED_EVENTS ||(global .__ zone_symbol__BLACK_LISTED_EVENTS = []);evtsArray.push('message');})(< any>窗口); 

现在我有一个可用的 window.unpatched 对象,它允许我完全退出Zone.JS,用于非常具体的任务,例如我正在工作的 IdleService 上:

 从'@ angular/core'导入{Injectable,NgZone};从'rxjs'导入{BehaviorSubject,Observable};从'rxjs/operators'导入{过滤器};/*这是在zone-config.ts中初始化的对象*/const未修补:{windowAddEventListener:typeof window.addEventListener;windowRemoveEventListener:typeof window.removeEventListener;documentAddEventListener:typeof document.addEventListener;documentRemoveEventListener:typeof document.removeEventListener;} = window ['unpatched'];/**管理用于检测页面上用户活动的事件处理程序,*通过鼠标或键盘事件.*/@Injectable({providerIn:'root'})导出类IdleService {private _idleForSecond $ =新的BehaviorSubject< number>(0);构造函数(区域:NgZone){const timerCallback =(idleFromSeconds:number):void =>this._idleForSecond $ .next(idleFromSeconds);this.registerIdleCallback(timerCallback);}私人registerIdleCallback(callback:(idleFromSeconds:number)=> void){让idleFromSeconds = 0;const resetCounter =()=>(idleFromSeconds = -1);//完全不在Zone.JS中运行unpatched.documentAddEventListener('mousemove',resetCounter);unpatched.documentAddEventListener('keypress',resetCounter);unpatched.documentAddEventListener('touchstart',resetCounter);//通常在区域中运行window.setInterval(()=>回调(++ idleFromSeconds),1000);}/**返回当用户不活动时间时发出的可观察对象*超过一定阈值.** @param seconds最小不活动时间,以秒为单位.*/byAtLeast(seconds:number):可观察的< number>{返回this._idleForSecond $ .pipe(filter(idleTime => idleTime> =秒));}/**返回一个观测值,该观测值自从* ladt用户的活动.*/by():可观察的< number>{返回this._idleForSecond $ .asObservable();}} 

现在(idleFromSeconds = -1)回调中的stackTrace根据需要为空.

希望这对其他人会有所帮助:这并不复杂,但是将所有位都放在适当位置会很麻烦.

First and foremost, I'm well aware of zone.runOutsideAngular(callback), documented here .

What this method does is running the callback in a different zone than the Angular one.

I need to attach a very quick callback to document's mousemove event (to count Idle time). I don't want to have the whole Zone.JS machinery of tasks appended to execution queues for this specific callback. I would really like to have the callbacks run in plain, unpatched browser runtime.

The event registration code I have is:

  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);
  }

The question is how can I get this code to use the unpatched document.addEventListener, accomplishing a complete separation from Zone.JS and true native performance?

解决方案

Well, it turned out to be simple, but not straightforward, so I'm going to post the solution I found here.

What I want to do is saving a usable document.addEventListener in a global object before Zone.JS patches the hell out of the browser's native objects.

This must be done before the loading of polyfills, because it's in there that Zone is loaded. And, it must not be done in plain code in polyfills.ts, because the import statements are processed before any other code.

So I need a file zone-config.ts in the same folder of polyfills.ts. In the latter an extra import is needed:

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.

In zone-config.ts I do the sleight-of-hand:

(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);

Now I have a window.unpatched object available, that allows me to opt out of Zone.JS completely, for very specific tasks, like the IdleService I was working on:

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

Now the stackTrace in the (idleFromSeconds = -1) callback is empty as desired.

Hope this can be of help to someone else: it's not complicated, but having all the bits in place was a bit of a chore.

这篇关于Angular-退出Zone.JS进行特定的回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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