Skip to content

Commit e694388

Browse files
authored
fix(material/datepicker): changed after checked error during overlapping open/close sequence (#25843)
Fixes that if the user triggers a closing sequence while the datepicker isn't finished opening, we end up triggering a "changed after checked" error. Fixes #25837.
1 parent acfc24c commit e694388

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

src/material/datepicker/datepicker-base.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
inject,
5252
} from '@angular/core';
5353
import {CanColor, DateAdapter, mixinColor, ThemePalette} from '@angular/material/core';
54+
import {AnimationEvent} from '@angular/animations';
5455
import {merge, Subject, Observable, Subscription} from 'rxjs';
5556
import {filter, take} from 'rxjs/operators';
5657
import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform';
@@ -119,7 +120,8 @@ const _MatDatepickerContentBase = mixinColor(
119120
host: {
120121
'class': 'mat-datepicker-content',
121122
'[@transformPanel]': '_animationState',
122-
'(@transformPanel.done)': '_animationDone.next()',
123+
'(@transformPanel.start)': '_handleAnimationEvent($event)',
124+
'(@transformPanel.done)': '_handleAnimationEvent($event)',
123125
'[class.mat-datepicker-content-touch]': 'datepicker.touchUi',
124126
},
125127
animations: [matDatepickerAnimations.transformPanel, matDatepickerAnimations.fadeInCalendar],
@@ -161,6 +163,9 @@ export class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>>
161163
/** Emits when an animation has finished. */
162164
readonly _animationDone = new Subject<void>();
163165

166+
/** Whether there is an in-progress animation. */
167+
_isAnimating = false;
168+
164169
/** Text for the close button. */
165170
_closeButtonText: string;
166171

@@ -240,6 +245,14 @@ export class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>>
240245
this._changeDetectorRef.markForCheck();
241246
}
242247

248+
_handleAnimationEvent(event: AnimationEvent) {
249+
this._isAnimating = event.phaseName === 'start';
250+
251+
if (!this._isAnimating) {
252+
this._animationDone.next();
253+
}
254+
}
255+
243256
_getSelected() {
244257
return this._model.selection as unknown as D | DateRange<D> | null;
245258
}
@@ -596,7 +609,9 @@ export abstract class MatDatepickerBase<
596609

597610
/** Open the calendar. */
598611
open(): void {
599-
if (this._opened || this.disabled) {
612+
// Skip reopening if there's an in-progress animation to avoid overlapping
613+
// sequences which can cause "changed after checked" errors. See #25837.
614+
if (this._opened || this.disabled || this._componentRef?.instance._isAnimating) {
600615
return;
601616
}
602617

@@ -612,7 +627,9 @@ export abstract class MatDatepickerBase<
612627

613628
/** Close the calendar. */
614629
close(): void {
615-
if (!this._opened) {
630+
// Skip reopening if there's an in-progress animation to avoid overlapping
631+
// sequences which can cause "changed after checked" errors. See #25837.
632+
if (!this._opened || this._componentRef?.instance._isAnimating) {
616633
return;
617634
}
618635

tools/public_api_guard/material/datepicker.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AbstractControl } from '@angular/forms';
99
import { AfterContentInit } from '@angular/core';
1010
import { AfterViewChecked } from '@angular/core';
1111
import { AfterViewInit } from '@angular/core';
12+
import { AnimationEvent as AnimationEvent_2 } from '@angular/animations';
1213
import { AnimationTriggerMetadata } from '@angular/animations';
1314
import { BooleanInput } from '@angular/cdk/coercion';
1415
import { CanColor } from '@angular/material/core';
@@ -382,8 +383,11 @@ export class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>> extend
382383
// (undocumented)
383384
_getSelected(): D | DateRange<D> | null;
384385
// (undocumented)
386+
_handleAnimationEvent(event: AnimationEvent_2): void;
387+
// (undocumented)
385388
_handleUserSelection(event: MatCalendarUserEvent<D | null>): void;
386389
_isAbove: boolean;
390+
_isAnimating: boolean;
387391
// (undocumented)
388392
ngAfterViewInit(): void;
389393
// (undocumented)

0 commit comments

Comments
 (0)