Angular - 退出 Zone.JS 以进行特定回调
Angular - Getting out of Zone.JS for specific callbacks
首先,我非常了解 zone.runOutsideAngular(callback)
,记录在案 here。
此方法的作用是 运行在 与 Angular 不同的 区域中设置回调。
我需要为 document
的 mousemove
事件附加一个非常快速的回调(以计算空闲时间)。我不想将整个 Zone.JS 任务机制附加到此特定回调的执行队列中。我真的很想在普通的、未打补丁的浏览器 运行time 中使用回调 运行。
我的活动注册码是:
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 和真正的原生性能?
我不确定我是否得到了你想要做的。我制作了一个小插件,展示了如何从 Angular.
外部进行事件回调
好吧,事实证明它很简单,但并不直接,所以我要post我在这里找到的解决方案。
我想做的是在全局对象中保存可用的 document.addEventListener
,然后 Zone.JS 修补浏览器的本机对象。
这必须在加载 polyfill 之前完成,因为 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();
}
}
现在 (idleFromSeconds = -1)
回调中的 stackTrace 为空。
希望这能对其他人有所帮助:它并不复杂,但是将所有的部分都准备好有点麻烦。
首先,我非常了解 zone.runOutsideAngular(callback)
,记录在案 here。
此方法的作用是 运行在 与 Angular 不同的 区域中设置回调。
我需要为 document
的 mousemove
事件附加一个非常快速的回调(以计算空闲时间)。我不想将整个 Zone.JS 任务机制附加到此特定回调的执行队列中。我真的很想在普通的、未打补丁的浏览器 运行time 中使用回调 运行。
我的活动注册码是:
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 和真正的原生性能?
我不确定我是否得到了你想要做的。我制作了一个小插件,展示了如何从 Angular.
外部进行事件回调好吧,事实证明它很简单,但并不直接,所以我要post我在这里找到的解决方案。
我想做的是在全局对象中保存可用的 document.addEventListener
,然后 Zone.JS 修补浏览器的本机对象。
这必须在加载 polyfill 之前完成,因为 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();
}
}
现在 (idleFromSeconds = -1)
回调中的 stackTrace 为空。
希望这能对其他人有所帮助:它并不复杂,但是将所有的部分都准备好有点麻烦。