From 5e01bae6124d6b1a374221e8688e9c7f365d1b6c Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 27 Feb 2024 23:28:29 +0000 Subject: [PATCH] refactor(material/datepicker): Remove use of zone onStable for focus and dropdown positioning --- src/material/datepicker/calendar-body.ts | 35 +++++++++++++--------- src/material/datepicker/calendar.spec.ts | 31 +++++-------------- src/material/datepicker/datepicker-base.ts | 11 ++++++- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/material/datepicker/calendar-body.ts b/src/material/datepicker/calendar-body.ts index 78f0ad2465e3..10519a2b5c27 100644 --- a/src/material/datepicker/calendar-body.ts +++ b/src/material/datepicker/calendar-body.ts @@ -21,6 +21,8 @@ import { OnDestroy, AfterViewChecked, inject, + afterNextRender, + Injector, } from '@angular/core'; import {take} from 'rxjs/operators'; import {NgClass} from '@angular/common'; @@ -189,6 +191,8 @@ export class MatCalendarBody implements OnChanges, OnDestroy, AfterView private _didDragSinceMouseDown = false; + private _injector = inject(Injector); + constructor( private _elementRef: ElementRef, private _ngZone: NgZone, @@ -310,21 +314,24 @@ export class MatCalendarBody implements OnChanges, OnDestroy, AfterView */ _focusActiveCell(movePreview = true) { this._ngZone.runOutsideAngular(() => { - this._ngZone.onStable.pipe(take(1)).subscribe(() => { - setTimeout(() => { - const activeCell: HTMLElement | null = this._elementRef.nativeElement.querySelector( - '.mat-calendar-body-active', - ); - - if (activeCell) { - if (!movePreview) { - this._skipNextFocus = true; + afterNextRender( + () => { + setTimeout(() => { + const activeCell: HTMLElement | null = this._elementRef.nativeElement.querySelector( + '.mat-calendar-body-active', + ); + + if (activeCell) { + if (!movePreview) { + this._skipNextFocus = true; + } + + activeCell.focus(); } - - activeCell.focus(); - } - }); - }); + }); + }, + {injector: this._injector}, + ); }); } diff --git a/src/material/datepicker/calendar.spec.ts b/src/material/datepicker/calendar.spec.ts index e23809995d58..70978c363ee9 100644 --- a/src/material/datepicker/calendar.spec.ts +++ b/src/material/datepicker/calendar.spec.ts @@ -4,17 +4,9 @@ import { dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent, - MockNgZone, } from '@angular/cdk/testing/private'; -import {Component, NgZone} from '@angular/core'; -import { - fakeAsync, - waitForAsync, - ComponentFixture, - inject, - TestBed, - tick, -} from '@angular/core/testing'; +import {Component} from '@angular/core'; +import {waitForAsync, ComponentFixture, inject, TestBed} from '@angular/core/testing'; import {DateAdapter, MatNativeDateModule} from '@angular/material/core'; import {DEC, FEB, JAN, JUL, NOV} from '../testing'; import {By} from '@angular/platform-browser'; @@ -23,16 +15,10 @@ import {MatDatepickerIntl} from './datepicker-intl'; import {MatDatepickerModule} from './datepicker-module'; describe('MatCalendar', () => { - let zone: MockNgZone; - beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [MatNativeDateModule, MatDatepickerModule], - providers: [ - MatDatepickerIntl, - {provide: NgZone, useFactory: () => (zone = new MockNgZone())}, - {provide: Directionality, useFactory: () => ({value: 'ltr'})}, - ], + providers: [MatDatepickerIntl, {provide: Directionality, useFactory: () => ({value: 'ltr'})}], declarations: [ // Test components. StandardCalendar, @@ -183,19 +169,19 @@ describe('MatCalendar', () => { expect(calendarBodyEl.getAttribute('tabindex')).toBe('-1'); }); - it('should not move focus to the active cell on init', () => { + it('should not move focus to the active cell on init', waitForAsync(async () => { const activeCell = calendarBodyEl.querySelector( '.mat-calendar-body-active', )! as HTMLElement; spyOn(activeCell, 'focus').and.callThrough(); fixture.detectChanges(); - zone.simulateZoneExit(); + await new Promise(resolve => setTimeout(resolve)); expect(activeCell.focus).not.toHaveBeenCalled(); - }); + })); - it('should move focus to the active cell when the view changes', fakeAsync(() => { + it('should move focus to the active cell when the view changes', waitForAsync(async () => { calendarInstance.currentView = 'multi-year'; fixture.detectChanges(); @@ -204,8 +190,7 @@ describe('MatCalendar', () => { )! as HTMLElement; spyOn(activeCell, 'focus').and.callThrough(); - zone.simulateZoneExit(); - tick(); + await new Promise(resolve => setTimeout(resolve)); expect(activeCell.focus).toHaveBeenCalled(); })); diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index e9b30cfbd487..706d8e97c1f1 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -51,6 +51,8 @@ import { OnInit, inject, booleanAttribute, + afterNextRender, + Injector, } from '@angular/core'; import {DateAdapter, ThemePalette} from '@angular/material/core'; import {AnimationEvent} from '@angular/animations'; @@ -509,6 +511,8 @@ export abstract class MatDatepickerBase< /** Emits when the datepicker's state changes. */ readonly stateChanges = new Subject(); + private _injector = inject(Injector); + constructor( private _overlay: Overlay, private _ngZone: NgZone, @@ -749,7 +753,12 @@ export abstract class MatDatepickerBase< // Update the position once the calendar has rendered. Only relevant in dropdown mode. if (!isDialog) { - this._ngZone.onStable.pipe(take(1)).subscribe(() => overlayRef.updatePosition()); + afterNextRender( + () => { + overlayRef.updatePosition(); + }, + {injector: this._injector}, + ); } }