From 94c630cfeba1c8c7d80a29072fcda9ea00804377 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Mon, 25 Mar 2024 13:48:04 -0500 Subject: [PATCH] refactor(material/table): remove use of zone onStable for style scheduling (#28747) --- .../table-scroll-container.spec.ts | 32 ++++---- src/cdk/table/coalesced-style-scheduler.ts | 40 ++++------ src/cdk/table/table.spec.ts | 78 +++++++++---------- .../column-resize/column-resize.spec.ts | 13 ++-- tools/public_api_guard/cdk/table.md | 5 +- 5 files changed, 81 insertions(+), 87 deletions(-) diff --git a/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts b/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts index 4b582253e285..8134cdc0f3ea 100644 --- a/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts +++ b/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts @@ -1,6 +1,6 @@ import {CollectionViewer, DataSource} from '@angular/cdk/collections'; import {Component, Type, ViewChild} from '@angular/core'; -import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {BehaviorSubject, combineLatest} from 'rxjs'; import {map} from 'rxjs/operators'; import {CdkTable, CdkTableModule} from '@angular/cdk/table'; @@ -48,10 +48,10 @@ describe('CdkTableScrollContainer', () => { dataRows = getRows(tableElement); }); - it('sets scrollbar track margin for sticky headers', fakeAsync(() => { + it('sets scrollbar track margin for sticky headers', waitForAsync(async () => { component.stickyHeaders = ['header-1', 'header-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); if (platform.FIREFOX) { // ::-webkit-scrollbar-track is not recognized by Firefox. @@ -66,7 +66,7 @@ describe('CdkTableScrollContainer', () => { component.stickyHeaders = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expect(scrollerStyle.getPropertyValue('margin-top')).toBe('0px'); expect(scrollerStyle.getPropertyValue('margin-right')).toBe('0px'); @@ -74,10 +74,10 @@ describe('CdkTableScrollContainer', () => { expect(scrollerStyle.getPropertyValue('margin-left')).toBe('0px'); })); - it('sets scrollbar track margin for sticky footers', fakeAsync(() => { + it('sets scrollbar track margin for sticky footers', waitForAsync(async () => { component.stickyFooters = ['footer-1', 'footer-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); if (platform.FIREFOX) { // ::-webkit-scrollbar-track is not recognized by Firefox. @@ -92,7 +92,7 @@ describe('CdkTableScrollContainer', () => { component.stickyFooters = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expect(scrollerStyle.getPropertyValue('margin-top')).toBe('0px'); expect(scrollerStyle.getPropertyValue('margin-right')).toBe('0px'); @@ -100,10 +100,10 @@ describe('CdkTableScrollContainer', () => { expect(scrollerStyle.getPropertyValue('margin-left')).toBe('0px'); })); - it('sets scrollbar track margin for sticky start columns', fakeAsync(() => { + it('sets scrollbar track margin for sticky start columns', waitForAsync(async () => { component.stickyStartColumns = ['column-1', 'column-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); if (platform.FIREFOX) { // ::-webkit-scrollbar-track is not recognized by Firefox. @@ -120,7 +120,7 @@ describe('CdkTableScrollContainer', () => { component.stickyStartColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expect(scrollerStyle.getPropertyValue('margin-top')).toBe('0px'); expect(scrollerStyle.getPropertyValue('margin-right')).toBe('0px'); @@ -128,10 +128,10 @@ describe('CdkTableScrollContainer', () => { expect(scrollerStyle.getPropertyValue('margin-left')).toBe('0px'); })); - it('sets scrollbar track margin for sticky end columns', fakeAsync(() => { + it('sets scrollbar track margin for sticky end columns', waitForAsync(async () => { component.stickyEndColumns = ['column-4', 'column-6']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); if (platform.FIREFOX) { // ::-webkit-scrollbar-track is not recognized by Firefox. @@ -148,7 +148,7 @@ describe('CdkTableScrollContainer', () => { component.stickyEndColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expect(scrollerStyle.getPropertyValue('margin-top')).toBe('0px'); expect(scrollerStyle.getPropertyValue('margin-right')).toBe('0px'); @@ -156,13 +156,13 @@ describe('CdkTableScrollContainer', () => { expect(scrollerStyle.getPropertyValue('margin-left')).toBe('0px'); })); - it('sets scrollbar track margin for a combination of sticky rows and columns', fakeAsync(() => { + it('sets scrollbar track margin for a combination of sticky rows and columns', waitForAsync(async () => { component.stickyHeaders = ['header-1']; component.stickyFooters = ['footer-3']; component.stickyStartColumns = ['column-1']; component.stickyEndColumns = ['column-6']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); if (platform.FIREFOX) { // ::-webkit-scrollbar-track is not recognized by Firefox. @@ -184,7 +184,7 @@ describe('CdkTableScrollContainer', () => { component.stickyStartColumns = []; component.stickyEndColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expect(scrollerStyle.getPropertyValue('margin-top')).toBe('0px'); expect(scrollerStyle.getPropertyValue('margin-right')).toBe('0px'); diff --git a/src/cdk/table/coalesced-style-scheduler.ts b/src/cdk/table/coalesced-style-scheduler.ts index baa0b99ea95c..f67b4871fbef 100644 --- a/src/cdk/table/coalesced-style-scheduler.ts +++ b/src/cdk/table/coalesced-style-scheduler.ts @@ -6,9 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable, NgZone, OnDestroy, InjectionToken} from '@angular/core'; -import {from, Subject} from 'rxjs'; -import {take, takeUntil} from 'rxjs/operators'; +import { + EnvironmentInjector, + Injectable, + InjectionToken, + NgZone, + afterNextRender, + inject, +} from '@angular/core'; /** * @docs-private @@ -31,11 +36,11 @@ export const _COALESCED_STYLE_SCHEDULER = new InjectionToken<_CoalescedStyleSche * @docs-private */ @Injectable() -export class _CoalescedStyleScheduler implements OnDestroy { +export class _CoalescedStyleScheduler { private _currentSchedule: _Schedule | null = null; - private readonly _destroyed = new Subject(); + private _injector = inject(EnvironmentInjector); - constructor(private readonly _ngZone: NgZone) {} + constructor(_unusedNgZone?: NgZone) {} /** * Schedules the specified task to run at the end of the current VM turn. @@ -56,12 +61,6 @@ export class _CoalescedStyleScheduler implements OnDestroy { this._currentSchedule!.endTasks.push(task); } - /** Prevent any further tasks from running. */ - ngOnDestroy() { - this._destroyed.next(); - this._destroyed.complete(); - } - private _createScheduleIfNeeded() { if (this._currentSchedule) { return; @@ -69,9 +68,8 @@ export class _CoalescedStyleScheduler implements OnDestroy { this._currentSchedule = new _Schedule(); - this._getScheduleObservable() - .pipe(takeUntil(this._destroyed)) - .subscribe(() => { + afterNextRender( + () => { while (this._currentSchedule!.tasks.length || this._currentSchedule!.endTasks.length) { const schedule = this._currentSchedule!; @@ -88,14 +86,8 @@ export class _CoalescedStyleScheduler implements OnDestroy { } this._currentSchedule = null; - }); - } - - private _getScheduleObservable() { - // Use onStable when in the context of an ongoing change detection cycle so that we - // do not accidentally trigger additional cycles. - return this._ngZone.isStable - ? from(Promise.resolve(undefined)) - : this._ngZone.onStable.pipe(take(1)); + }, + {injector: this._injector}, + ); } } diff --git a/src/cdk/table/table.spec.ts b/src/cdk/table/table.spec.ts index 4fa833885d3f..65013c0cb9fb 100644 --- a/src/cdk/table/table.spec.ts +++ b/src/cdk/table/table.spec.ts @@ -11,7 +11,7 @@ import { AfterViewInit, ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, fakeAsync, flush, flushMicrotasks, TestBed} from '@angular/core/testing'; +import {ComponentFixture, fakeAsync, flush, TestBed, waitForAsync} from '@angular/core/testing'; import {BehaviorSubject, combineLatest, Observable, of as observableOf} from 'rxjs'; import {map} from 'rxjs/operators'; import {CdkColumnDef} from './cell'; @@ -966,10 +966,10 @@ describe('CdkTable', () => { dataRows = getRows(tableElement); }); - it('should stick and unstick headers', fakeAsync(() => { + it('should stick and unstick headers', waitForAsync(async () => { component.stickyHeaders = ['header-1', 'header-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectStickyStyles(headerRows[0], '100', {top: '0px'}); expectStickyBorderClass(headerRows[0]); @@ -997,7 +997,7 @@ describe('CdkTable', () => { component.stickyHeaders = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectNoStickyStyles(headerRows); expect(component.mostRecentStickyHeaderRowsUpdate).toEqual({ sizes: [], @@ -1013,10 +1013,10 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should stick and unstick footers', fakeAsync(() => { + it('should stick and unstick footers', waitForAsync(async () => { component.stickyFooters = ['footer-1', 'footer-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectStickyStyles(footerRows[0], '10', { bottom: footerRows[1].getBoundingClientRect().height + 'px', @@ -1044,7 +1044,7 @@ describe('CdkTable', () => { component.stickyFooters = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectNoStickyStyles(footerRows); expect(component.mostRecentStickyHeaderRowsUpdate).toEqual({ sizes: [], @@ -1060,20 +1060,20 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should stick the correct footer row', fakeAsync(() => { + it('should stick the correct footer row', waitForAsync(async () => { component.stickyFooters = ['footer-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectStickyStyles(footerRows[2], '10', {bottom: '0px'}); expectStickyBorderClass(footerRows[2], {bottom: true}); expectNoStickyStyles([footerRows[0], footerRows[1]]); })); - it('should stick and unstick left columns', fakeAsync(() => { + it('should stick and unstick left columns', waitForAsync(async () => { component.stickyStartColumns = ['column-1', 'column-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => { let cells = getHeaderCells(row); @@ -1120,7 +1120,7 @@ describe('CdkTable', () => { component.stickyStartColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => expectNoStickyStyles(getHeaderCells(row))); dataRows.forEach(row => expectNoStickyStyles(getCells(row))); footerRows.forEach(row => expectNoStickyStyles(getFooterCells(row))); @@ -1138,10 +1138,10 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should stick and unstick right columns', fakeAsync(() => { + it('should stick and unstick right columns', waitForAsync(async () => { component.stickyEndColumns = ['column-4', 'column-6']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => { let cells = getHeaderCells(row); @@ -1188,7 +1188,7 @@ describe('CdkTable', () => { component.stickyEndColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => expectNoStickyStyles(getHeaderCells(row))); dataRows.forEach(row => expectNoStickyStyles(getCells(row))); footerRows.forEach(row => expectNoStickyStyles(getFooterCells(row))); @@ -1206,12 +1206,12 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should reverse directions for sticky columns in rtl', fakeAsync(() => { + it('should reverse directions for sticky columns in rtl', waitForAsync(async () => { component.dir = 'rtl'; component.stickyStartColumns = ['column-1', 'column-2']; component.stickyEndColumns = ['column-5', 'column-6']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); const firstColumnWidth = getHeaderCells(headerRows[0])[0].getBoundingClientRect().width; const lastColumnWidth = getHeaderCells(headerRows[0])[5].getBoundingClientRect().width; @@ -1249,13 +1249,13 @@ describe('CdkTable', () => { expectStickyBorderClass(footerCells[5]); })); - it('should stick and unstick combination of sticky header, footer, and columns', fakeAsync(() => { + it('should stick and unstick combination of sticky header, footer, and columns', waitForAsync(async () => { component.stickyHeaders = ['header-1']; component.stickyFooters = ['footer-3']; component.stickyStartColumns = ['column-1']; component.stickyEndColumns = ['column-6']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); let headerCells = getHeaderCells(headerRows[0]); expectStickyStyles(headerRows[0], '100', {top: '0px'}); @@ -1308,7 +1308,7 @@ describe('CdkTable', () => { component.stickyStartColumns = []; component.stickyEndColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => expectNoStickyStyles([row, ...getHeaderCells(row)])); dataRows.forEach(row => expectNoStickyStyles([row, ...getCells(row)])); @@ -1342,10 +1342,10 @@ describe('CdkTable', () => { dataRows = getRows(tableElement); }); - it('should stick and unstick headers', fakeAsync(() => { + it('should stick and unstick headers', waitForAsync(async () => { component.stickyHeaders = ['header-1', 'header-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); getHeaderCells(headerRows[0]).forEach(cell => { expectStickyStyles(cell, '100', {top: '0px'}); @@ -1377,7 +1377,7 @@ describe('CdkTable', () => { component.stickyHeaders = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectNoStickyStyles(headerRows); // No sticky styles on rows for native table headerRows.forEach(row => expectNoStickyStyles(getHeaderCells(row))); expect(component.mostRecentStickyHeaderRowsUpdate).toEqual({ @@ -1394,10 +1394,10 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should stick and unstick footers', fakeAsync(() => { + it('should stick and unstick footers', waitForAsync(async () => { component.stickyFooters = ['footer-1', 'footer-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); getFooterCells(footerRows[2]).forEach(cell => { expectStickyStyles(cell, '10', {bottom: '0px'}); @@ -1429,7 +1429,7 @@ describe('CdkTable', () => { component.stickyFooters = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectNoStickyStyles(footerRows); // No sticky styles on rows for native table footerRows.forEach(row => expectNoStickyStyles(getFooterCells(row))); expect(component.mostRecentStickyHeaderRowsUpdate).toEqual({ @@ -1446,29 +1446,29 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should stick tfoot when all rows are stuck', fakeAsync(() => { + it('should stick tfoot when all rows are stuck', waitForAsync(async () => { const tfoot = tableElement.querySelector('tfoot'); component.stickyFooters = ['footer-1']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectNoStickyStyles([tfoot]); component.stickyFooters = ['footer-1', 'footer-2', 'footer-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectStickyStyles(tfoot, '10', {bottom: '0px'}); expectStickyBorderClass(tfoot); component.stickyFooters = ['footer-1', 'footer-2']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); expectNoStickyStyles([tfoot]); })); - it('should stick and unstick left columns', fakeAsync(() => { + it('should stick and unstick left columns', waitForAsync(async () => { component.stickyStartColumns = ['column-1', 'column-3']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => { let cells = getHeaderCells(row); @@ -1515,7 +1515,7 @@ describe('CdkTable', () => { component.stickyStartColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => expectNoStickyStyles(getHeaderCells(row))); dataRows.forEach(row => expectNoStickyStyles(getCells(row))); footerRows.forEach(row => expectNoStickyStyles(getFooterCells(row))); @@ -1533,10 +1533,10 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should stick and unstick right columns', fakeAsync(() => { + it('should stick and unstick right columns', waitForAsync(async () => { component.stickyEndColumns = ['column-4', 'column-6']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => { let cells = getHeaderCells(row); @@ -1583,7 +1583,7 @@ describe('CdkTable', () => { component.stickyEndColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => expectNoStickyStyles(getHeaderCells(row))); dataRows.forEach(row => expectNoStickyStyles(getCells(row))); footerRows.forEach(row => expectNoStickyStyles(getFooterCells(row))); @@ -1601,13 +1601,13 @@ describe('CdkTable', () => { expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); })); - it('should stick and unstick combination of sticky header, footer, and columns', fakeAsync(() => { + it('should stick and unstick combination of sticky header, footer, and columns', waitForAsync(async () => { component.stickyHeaders = ['header-1']; component.stickyFooters = ['footer-3']; component.stickyStartColumns = ['column-1']; component.stickyEndColumns = ['column-6']; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); const headerCells = getHeaderCells(headerRows[0]); expectStickyStyles(headerCells[0], '101', {top: '0px', left: '0px'}); @@ -1670,7 +1670,7 @@ describe('CdkTable', () => { component.stickyStartColumns = []; component.stickyEndColumns = []; fixture.detectChanges(); - flushMicrotasks(); + await new Promise(r => setTimeout(r)); headerRows.forEach(row => expectNoStickyStyles([row, ...getHeaderCells(row)])); dataRows.forEach(row => expectNoStickyStyles([row, ...getCells(row)])); diff --git a/src/material-experimental/column-resize/column-resize.spec.ts b/src/material-experimental/column-resize/column-resize.spec.ts index 45f6dbe16413..a67dcf04c26f 100644 --- a/src/material-experimental/column-resize/column-resize.spec.ts +++ b/src/material-experimental/column-resize/column-resize.spec.ts @@ -1,13 +1,14 @@ -import {Component, Directive, ElementRef, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing'; import {BidiModule} from '@angular/cdk/bidi'; import {DataSource} from '@angular/cdk/collections'; -import {dispatchKeyboardEvent} from '../../cdk/testing/private'; import {ESCAPE} from '@angular/cdk/keycodes'; +import {ChangeDetectionStrategy, Component, Directive, ElementRef, ViewChild} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing'; import {MatTableModule} from '@angular/material/table'; import {BehaviorSubject} from 'rxjs'; +import {dispatchKeyboardEvent} from '../../cdk/testing/private'; import {ColumnSize} from '@angular/cdk-experimental/column-resize'; +import {AbstractMatColumnResize} from './column-resize-directives/common'; import { MatColumnResize, MatColumnResizeFlex, @@ -16,7 +17,6 @@ import { MatDefaultEnabledColumnResizeFlex, MatDefaultEnabledColumnResizeModule, } from './index'; -import {AbstractMatColumnResize} from './column-resize-directives/common'; function getDefaultEnabledDirectiveStrings() { return { @@ -444,6 +444,7 @@ describe('Material Popover Edit', () => { const initialThumbPosition = component.getOverlayThumbPosition(1); component.updateResizeWithMouseInProgress(5); + fixture.detectChanges(); flushMicrotasks(); let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; @@ -461,6 +462,7 @@ describe('Material Popover Edit', () => { (expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 5); component.updateResizeWithMouseInProgress(1); + fixture.detectChanges(); flushMicrotasks(); thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; @@ -506,6 +508,7 @@ describe('Material Popover Edit', () => { const initialThumbPosition = component.getOverlayThumbPosition(1); component.updateResizeWithMouseInProgress(5); + fixture.detectChanges(); flushMicrotasks(); let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; @@ -541,7 +544,7 @@ describe('Material Popover Edit', () => { expect(resize).toBe(null); component.resizeColumnWithMouse(1, 5); - flushMicrotasks(); + fixture.detectChanges(); expect(resize).toEqual({columnId: 'name', size: initialColumnWidth + 5} as any); diff --git a/tools/public_api_guard/cdk/table.md b/tools/public_api_guard/cdk/table.md index 779718db646c..3587400512f0 100644 --- a/tools/public_api_guard/cdk/table.md +++ b/tools/public_api_guard/cdk/table.md @@ -439,9 +439,8 @@ export interface CellDef { export const _COALESCED_STYLE_SCHEDULER: InjectionToken<_CoalescedStyleScheduler>; // @public -export class _CoalescedStyleScheduler implements OnDestroy { - constructor(_ngZone: NgZone); - ngOnDestroy(): void; +export class _CoalescedStyleScheduler { + constructor(_unusedNgZone?: NgZone); schedule(task: () => unknown): void; scheduleEnd(task: () => unknown): void; // (undocumented)