feat: add a flag in bootstrap to enable coalesce event change detection to improve performance (#30533)
PR Close #30533
This commit is contained in:
		
							parent
							
								
									c5894e08bc
								
							
						
					
					
						commit
						44623a1161
					
				| @ -219,6 +219,27 @@ export interface BootstrapOptions { | |||||||
|    * - `noop` - Use `NoopNgZone` which does nothing. |    * - `noop` - Use `NoopNgZone` which does nothing. | ||||||
|    */ |    */ | ||||||
|   ngZone?: NgZone|'zone.js'|'noop'; |   ngZone?: NgZone|'zone.js'|'noop'; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Optionally specify coalescing event change detections or not. | ||||||
|  |    * Consider the following case. | ||||||
|  |    * | ||||||
|  |    * <div (click)="doSomething()"> | ||||||
|  |    *   <button (click)="doSomethingElse()"></button> | ||||||
|  |    * </div> | ||||||
|  |    * | ||||||
|  |    * When button is clicked, because of the event bubbling, both | ||||||
|  |    * event handlers will be called and 2 change detections will be | ||||||
|  |    * triggered. We can colesce such kind of events to only trigger | ||||||
|  |    * change detection only once. | ||||||
|  |    * | ||||||
|  |    * By default, this option will be false. So the events will not be | ||||||
|  |    * coalesced and the change detection will be triggered multiple times. | ||||||
|  |    * And if this option be set to true, the change detection will be | ||||||
|  |    * triggered async by scheduling a animation frame. So in the case above, | ||||||
|  |    * the change detection will only be trigged once. | ||||||
|  |    */ | ||||||
|  |   ngZoneEventCoalescing?: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -269,7 +290,8 @@ export class PlatformRef { | |||||||
|     // So we create a mini parent injector that just contains the new NgZone and
 |     // So we create a mini parent injector that just contains the new NgZone and
 | ||||||
|     // pass that as parent to the NgModuleFactory.
 |     // pass that as parent to the NgModuleFactory.
 | ||||||
|     const ngZoneOption = options ? options.ngZone : undefined; |     const ngZoneOption = options ? options.ngZone : undefined; | ||||||
|     const ngZone = getNgZone(ngZoneOption); |     const ngZoneEventCoalescing = (options && options.ngZoneEventCoalescing) || false; | ||||||
|  |     const ngZone = getNgZone(ngZoneOption, ngZoneEventCoalescing); | ||||||
|     const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; |     const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; | ||||||
|     // Attention: Don't use ApplicationRef.run here,
 |     // Attention: Don't use ApplicationRef.run here,
 | ||||||
|     // as we want to be sure that all possible constructor calls are inside `ngZone.run`!
 |     // as we want to be sure that all possible constructor calls are inside `ngZone.run`!
 | ||||||
| @ -365,14 +387,17 @@ export class PlatformRef { | |||||||
|   get destroyed() { return this._destroyed; } |   get destroyed() { return this._destroyed; } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getNgZone(ngZoneOption?: NgZone | 'zone.js' | 'noop'): NgZone { | function getNgZone( | ||||||
|  |     ngZoneOption: NgZone | 'zone.js' | 'noop' | undefined, ngZoneEventCoalescing: boolean): NgZone { | ||||||
|   let ngZone: NgZone; |   let ngZone: NgZone; | ||||||
| 
 | 
 | ||||||
|   if (ngZoneOption === 'noop') { |   if (ngZoneOption === 'noop') { | ||||||
|     ngZone = new NoopNgZone(); |     ngZone = new NoopNgZone(); | ||||||
|   } else { |   } else { | ||||||
|     ngZone = (ngZoneOption === 'zone.js' ? undefined : ngZoneOption) || |     ngZone = (ngZoneOption === 'zone.js' ? undefined : ngZoneOption) || new NgZone({ | ||||||
|         new NgZone({enableLongStackTrace: isDevMode()}); |                enableLongStackTrace: isDevMode(), | ||||||
|  |                shouldCoalesceEventChangeDetection: ngZoneEventCoalescing | ||||||
|  |              }); | ||||||
|   } |   } | ||||||
|   return ngZone; |   return ngZone; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								packages/core/src/util/raf.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								packages/core/src/util/raf.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | /** | ||||||
|  |  * @license | ||||||
|  |  * Copyright Google Inc. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Use of this source code is governed by an MIT-style license that can be | ||||||
|  |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  |  */ | ||||||
|  | import {global} from './global'; | ||||||
|  | 
 | ||||||
|  | export function getNativeRequestAnimationFrame() { | ||||||
|  |   let nativeRequestAnimationFrame: (callback: FrameRequestCallback) => number = | ||||||
|  |       global['requestAnimationFrame']; | ||||||
|  |   let nativeCancelAnimationFrame: (handle: number) => void = global['cancelAnimationFrame']; | ||||||
|  |   if (typeof Zone !== 'undefined' && nativeRequestAnimationFrame && nativeCancelAnimationFrame) { | ||||||
|  |     // use unpatched version of requestAnimationFrame(native delegate) if possible
 | ||||||
|  |     // to avoid another Change detection
 | ||||||
|  |     const unpatchedRequestAnimationFrame = | ||||||
|  |         (nativeRequestAnimationFrame as any)[(Zone as any).__symbol__('OriginalDelegate')]; | ||||||
|  |     if (unpatchedRequestAnimationFrame) { | ||||||
|  |       nativeRequestAnimationFrame = unpatchedRequestAnimationFrame; | ||||||
|  |     } | ||||||
|  |     const unpatchedCancelAnimationFrame = | ||||||
|  |         (nativeCancelAnimationFrame as any)[(Zone as any).__symbol__('OriginalDelegate')]; | ||||||
|  |     if (unpatchedCancelAnimationFrame) { | ||||||
|  |       nativeCancelAnimationFrame = unpatchedCancelAnimationFrame; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return {nativeRequestAnimationFrame, nativeCancelAnimationFrame}; | ||||||
|  | } | ||||||
| @ -7,6 +7,9 @@ | |||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import {EventEmitter} from '../event_emitter'; | import {EventEmitter} from '../event_emitter'; | ||||||
|  | import {global} from '../util/global'; | ||||||
|  | import {getNativeRequestAnimationFrame} from '../util/raf'; | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * An injectable service for executing work inside or outside of the Angular zone. |  * An injectable service for executing work inside or outside of the Angular zone. | ||||||
| @ -83,8 +86,8 @@ import {EventEmitter} from '../event_emitter'; | |||||||
|  * @publicApi |  * @publicApi | ||||||
|  */ |  */ | ||||||
| export class NgZone { | export class NgZone { | ||||||
|   readonly hasPendingMicrotasks: boolean = false; |  | ||||||
|   readonly hasPendingMacrotasks: boolean = false; |   readonly hasPendingMacrotasks: boolean = false; | ||||||
|  |   readonly hasPendingMicrotasks: boolean = false; | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Whether there are no outstanding microtasks or macrotasks. |    * Whether there are no outstanding microtasks or macrotasks. | ||||||
| @ -115,7 +118,8 @@ export class NgZone { | |||||||
|    */ |    */ | ||||||
|   readonly onError: EventEmitter<any> = new EventEmitter(false); |   readonly onError: EventEmitter<any> = new EventEmitter(false); | ||||||
| 
 | 
 | ||||||
|   constructor({enableLongStackTrace = false}) { | 
 | ||||||
|  |   constructor({enableLongStackTrace = false, shouldCoalesceEventChangeDetection = false}) { | ||||||
|     if (typeof Zone == 'undefined') { |     if (typeof Zone == 'undefined') { | ||||||
|       throw new Error(`In this configuration Angular requires Zone.js`); |       throw new Error(`In this configuration Angular requires Zone.js`); | ||||||
|     } |     } | ||||||
| @ -138,6 +142,9 @@ export class NgZone { | |||||||
|       self._inner = self._inner.fork((Zone as any)['longStackTraceZoneSpec']); |       self._inner = self._inner.fork((Zone as any)['longStackTraceZoneSpec']); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     self.shouldCoalesceEventChangeDetection = shouldCoalesceEventChangeDetection; | ||||||
|  |     self.lastRequestAnimationFrameId = -1; | ||||||
|  |     self.nativeRequestAnimationFrame = getNativeRequestAnimationFrame().nativeRequestAnimationFrame; | ||||||
|     forkInnerZoneWithAngularBehavior(self); |     forkInnerZoneWithAngularBehavior(self); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -222,15 +229,18 @@ export class NgZone { | |||||||
| function noop() {} | function noop() {} | ||||||
| const EMPTY_PAYLOAD = {}; | const EMPTY_PAYLOAD = {}; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| interface NgZonePrivate extends NgZone { | interface NgZonePrivate extends NgZone { | ||||||
|   _outer: Zone; |   _outer: Zone; | ||||||
|   _inner: Zone; |   _inner: Zone; | ||||||
|   _nesting: number; |   _nesting: number; | ||||||
|  |   _hasPendingMicrotasks: boolean; | ||||||
| 
 | 
 | ||||||
|   hasPendingMicrotasks: boolean; |  | ||||||
|   hasPendingMacrotasks: boolean; |   hasPendingMacrotasks: boolean; | ||||||
|  |   hasPendingMicrotasks: boolean; | ||||||
|  |   lastRequestAnimationFrameId: number; | ||||||
|   isStable: boolean; |   isStable: boolean; | ||||||
|  |   shouldCoalesceEventChangeDetection: boolean; | ||||||
|  |   nativeRequestAnimationFrame: (callback: FrameRequestCallback) => number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function checkStable(zone: NgZonePrivate) { | function checkStable(zone: NgZonePrivate) { | ||||||
| @ -251,16 +261,35 @@ function checkStable(zone: NgZonePrivate) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function delayChangeDetectionForEvents(zone: NgZonePrivate) { | ||||||
|  |   if (zone.lastRequestAnimationFrameId !== -1) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   zone.lastRequestAnimationFrameId = zone.nativeRequestAnimationFrame.call(global, () => { | ||||||
|  |     zone.lastRequestAnimationFrameId = -1; | ||||||
|  |     updateMicroTaskStatus(zone); | ||||||
|  |     checkStable(zone); | ||||||
|  |   }); | ||||||
|  |   updateMicroTaskStatus(zone); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) { | function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) { | ||||||
|  |   const delayChangeDetectionForEventsDelegate = () => { delayChangeDetectionForEvents(zone); }; | ||||||
|  |   const maybeDelayChangeDetection = !!zone.shouldCoalesceEventChangeDetection && | ||||||
|  |       zone.nativeRequestAnimationFrame && delayChangeDetectionForEventsDelegate; | ||||||
|   zone._inner = zone._inner.fork({ |   zone._inner = zone._inner.fork({ | ||||||
|     name: 'angular', |     name: 'angular', | ||||||
|     properties: <any>{'isAngularZone': true}, |     properties: | ||||||
|  |         <any>{'isAngularZone': true, 'maybeDelayChangeDetection': maybeDelayChangeDetection}, | ||||||
|     onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, |     onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, | ||||||
|                    applyArgs: any): any => { |                    applyArgs: any): any => { | ||||||
|       try { |       try { | ||||||
|         onEnter(zone); |         onEnter(zone); | ||||||
|         return delegate.invokeTask(target, task, applyThis, applyArgs); |         return delegate.invokeTask(target, task, applyThis, applyArgs); | ||||||
|       } finally { |       } finally { | ||||||
|  |         if (maybeDelayChangeDetection && task.type === 'eventTask') { | ||||||
|  |           maybeDelayChangeDetection(); | ||||||
|  |         } | ||||||
|         onLeave(zone); |         onLeave(zone); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| @ -283,7 +312,8 @@ function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) { | |||||||
|             // We are only interested in hasTask events which originate from our zone
 |             // We are only interested in hasTask events which originate from our zone
 | ||||||
|             // (A child hasTask event is not interesting to us)
 |             // (A child hasTask event is not interesting to us)
 | ||||||
|             if (hasTaskState.change == 'microTask') { |             if (hasTaskState.change == 'microTask') { | ||||||
|               zone.hasPendingMicrotasks = hasTaskState.microTask; |               zone._hasPendingMicrotasks = hasTaskState.microTask; | ||||||
|  |               updateMicroTaskStatus(zone); | ||||||
|               checkStable(zone); |               checkStable(zone); | ||||||
|             } else if (hasTaskState.change == 'macroTask') { |             } else if (hasTaskState.change == 'macroTask') { | ||||||
|               zone.hasPendingMacrotasks = hasTaskState.macroTask; |               zone.hasPendingMacrotasks = hasTaskState.macroTask; | ||||||
| @ -299,6 +329,15 @@ function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function updateMicroTaskStatus(zone: NgZonePrivate) { | ||||||
|  |   if (zone._hasPendingMicrotasks || | ||||||
|  |       (zone.shouldCoalesceEventChangeDetection && zone.lastRequestAnimationFrameId !== -1)) { | ||||||
|  |     zone.hasPendingMicrotasks = true; | ||||||
|  |   } else { | ||||||
|  |     zone.hasPendingMicrotasks = false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function onEnter(zone: NgZonePrivate) { | function onEnter(zone: NgZonePrivate) { | ||||||
|   zone._nesting++; |   zone._nesting++; | ||||||
|   if (zone.isStable) { |   if (zone.isStable) { | ||||||
|  | |||||||
| @ -95,7 +95,7 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec' | |||||||
|             resolvedPromise.then((_) => { throw new Error('async'); }); |             resolvedPromise.then((_) => { throw new Error('async'); }); | ||||||
|             flushMicrotasks(); |             flushMicrotasks(); | ||||||
|           })(); |           })(); | ||||||
|         }).toThrowError(/Uncaught \(in promise\): Error: async/); |         }).toThrow(); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('should complain if a test throws an exception', () => { |       it('should complain if a test throws an exception', () => { | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import {EventEmitter, Injectable, NgZone} from '@angular/core'; | |||||||
| export class MockNgZone extends NgZone { | export class MockNgZone extends NgZone { | ||||||
|   onStable: EventEmitter<any> = new EventEmitter(false); |   onStable: EventEmitter<any> = new EventEmitter(false); | ||||||
| 
 | 
 | ||||||
|   constructor() { super({enableLongStackTrace: false}); } |   constructor() { super({enableLongStackTrace: false, shouldCoalesceEventChangeDetection: false}); } | ||||||
| 
 | 
 | ||||||
|   run(fn: Function): any { return fn(); } |   run(fn: Function): any { return fn(); } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -399,7 +399,8 @@ export class TestBedViewEngine implements TestBed { | |||||||
|       overrideComponentView(component, compFactory); |       overrideComponentView(component, compFactory); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const ngZone = new NgZone({enableLongStackTrace: true}); |     const ngZone = | ||||||
|  |         new NgZone({enableLongStackTrace: true, shouldCoalesceEventChangeDetection: false}); | ||||||
|     const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; |     const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; | ||||||
|     const ngZoneInjector = Injector.create({ |     const ngZoneInjector = Injector.create({ | ||||||
|       providers: providers, |       providers: providers, | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import {createMouseEvent, el} from '../../../testing/src/browser_util'; | |||||||
|   let zone: NgZone; |   let zone: NgZone; | ||||||
| 
 | 
 | ||||||
|   describe('EventManager', () => { |   describe('EventManager', () => { | ||||||
| 
 |  | ||||||
|     beforeEach(() => { |     beforeEach(() => { | ||||||
|       doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument(); |       doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument(); | ||||||
|       zone = new NgZone({}); |       zone = new NgZone({}); | ||||||
| @ -296,7 +295,7 @@ import {createMouseEvent, el} from '../../../testing/src/browser_util'; | |||||||
|       expect(receivedEvents).toEqual([]); |       expect(receivedEvents).toEqual([]); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should run blockListedEvents handler outside of ngZone', () => { |     it('should run blackListedEvents handler outside of ngZone', () => { | ||||||
|       const Zone = (window as any)['Zone']; |       const Zone = (window as any)['Zone']; | ||||||
|       const element = el('<div><div></div></div>'); |       const element = el('<div><div></div></div>'); | ||||||
|       doc.body.appendChild(element); |       doc.body.appendChild(element); | ||||||
| @ -312,13 +311,45 @@ import {createMouseEvent, el} from '../../../testing/src/browser_util'; | |||||||
|       let remover = manager.addEventListener(element, 'scroll', handler); |       let remover = manager.addEventListener(element, 'scroll', handler); | ||||||
|       getDOM().dispatchEvent(element, dispatchedEvent); |       getDOM().dispatchEvent(element, dispatchedEvent); | ||||||
|       expect(receivedEvent).toBe(dispatchedEvent); |       expect(receivedEvent).toBe(dispatchedEvent); | ||||||
|       expect(receivedZone.name).toBe(Zone.root.name); |       expect(receivedZone.name).not.toEqual('angular'); | ||||||
| 
 | 
 | ||||||
|       receivedEvent = null; |       receivedEvent = null; | ||||||
|       remover && remover(); |       remover && remover(); | ||||||
|       getDOM().dispatchEvent(element, dispatchedEvent); |       getDOM().dispatchEvent(element, dispatchedEvent); | ||||||
|       expect(receivedEvent).toBe(null); |       expect(receivedEvent).toBe(null); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     it('should only trigger one Change detection when bubbling', (done: DoneFn) => { | ||||||
|  |       doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument(); | ||||||
|  |       zone = new NgZone({shouldCoalesceEventChangeDetection: true}); | ||||||
|  |       domEventPlugin = new DomEventsPlugin(doc, zone, null); | ||||||
|  |       const element = el('<div></div>'); | ||||||
|  |       const child = el('<div></div>'); | ||||||
|  |       element.appendChild(child); | ||||||
|  |       doc.body.appendChild(element); | ||||||
|  |       const dispatchedEvent = createMouseEvent('click'); | ||||||
|  |       let receivedEvents: any = []; | ||||||
|  |       let stables: any = []; | ||||||
|  |       const handler = (e: any) => { receivedEvents.push(e); }; | ||||||
|  |       const manager = new EventManager([domEventPlugin], zone); | ||||||
|  |       let removerChild: any; | ||||||
|  |       let removerParent: any; | ||||||
|  | 
 | ||||||
|  |       zone.run(() => { | ||||||
|  |         removerChild = manager.addEventListener(child, 'click', handler); | ||||||
|  |         removerParent = manager.addEventListener(element, 'click', handler); | ||||||
|  |       }); | ||||||
|  |       zone.onStable.subscribe((isStable: any) => { stables.push(isStable); }); | ||||||
|  |       getDOM().dispatchEvent(child, dispatchedEvent); | ||||||
|  |       requestAnimationFrame(() => { | ||||||
|  |         expect(receivedEvents.length).toBe(2); | ||||||
|  |         expect(stables.length).toBe(1); | ||||||
|  | 
 | ||||||
|  |         removerChild && removerChild(); | ||||||
|  |         removerParent && removerParent(); | ||||||
|  |         done(); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| })(); | })(); | ||||||
| 
 | 
 | ||||||
| @ -332,12 +363,12 @@ class FakeEventManagerPlugin extends EventManagerPlugin { | |||||||
| 
 | 
 | ||||||
|   addEventListener(element: any, eventName: string, handler: Function) { |   addEventListener(element: any, eventName: string, handler: Function) { | ||||||
|     this.eventHandler[eventName] = handler; |     this.eventHandler[eventName] = handler; | ||||||
|     return () => { delete (this.eventHandler[eventName]); }; |     return () => { delete this.eventHandler[eventName]; }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class FakeNgZone extends NgZone { | class FakeNgZone extends NgZone { | ||||||
|   constructor() { super({enableLongStackTrace: false}); } |   constructor() { super({enableLongStackTrace: false, shouldCoalesceEventChangeDetection: true}); } | ||||||
|   run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { return fn(); } |   run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { return fn(); } | ||||||
|   runOutsideAngular(fn: Function) { return fn(); } |   runOutsideAngular(fn: Function) { return fn(); } | ||||||
| } | } | ||||||
|  | |||||||
| @ -175,7 +175,7 @@ export function stringifyElement(el: any /** TODO #9100 */): string { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function createNgZone(): NgZone { | export function createNgZone(): NgZone { | ||||||
|   return new NgZone({enableLongStackTrace: true}); |   return new NgZone({enableLongStackTrace: true, shouldCoalesceEventChangeDetection: false}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function isCommentNode(node: Node): boolean { | export function isCommentNode(node: Node): boolean { | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								tools/public_api_guard/core/core.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								tools/public_api_guard/core/core.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -630,8 +630,9 @@ export declare class NgZone { | |||||||
|     readonly onMicrotaskEmpty: EventEmitter<any>; |     readonly onMicrotaskEmpty: EventEmitter<any>; | ||||||
|     readonly onStable: EventEmitter<any>; |     readonly onStable: EventEmitter<any>; | ||||||
|     readonly onUnstable: EventEmitter<any>; |     readonly onUnstable: EventEmitter<any>; | ||||||
|     constructor({ enableLongStackTrace }: { |     constructor({ enableLongStackTrace, shouldCoalesceEventChangeDetection }: { | ||||||
|         enableLongStackTrace?: boolean | undefined; |         enableLongStackTrace?: boolean | undefined; | ||||||
|  |         shouldCoalesceEventChangeDetection?: boolean | undefined; | ||||||
|     }); |     }); | ||||||
|     run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T; |     run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T; | ||||||
|     runGuarded<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T; |     runGuarded<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user