Skip to content

Commit 3f9f22b

Browse files
authored
fix(material/datepicker): update parse error with signal forms when value stays null (#33951)
When the user types an unparseable value into an empty datepicker input, or erases an unparseable value, the model value stays `null`. Reactive forms re-run the validators on every `setValue`, but signal forms skip writes that don't change the value, so the `matDatepickerParse` error was neither added nor cleared in those cases. Notify the forms module that the validators changed whenever the parse state flips while the value stays the same, including the `null` to `null` case. Fixes #33740.
1 parent 9a40cad commit 3f9f22b

2 files changed

Lines changed: 60 additions & 3 deletions

File tree

‎src/material/datepicker/datepicker-input-base.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,13 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
341341
if (value && !this.value) {
342342
this._cvaOnChange(date);
343343
}
344+
}
344345

345-
if (lastValueWasValid !== this._lastValueValid) {
346-
this._validatorOnChange();
347-
}
346+
// Re-run the validators if the parse state changed while the value stayed the same.
347+
// This also covers going from one null to another (e.g. clearing an unparseable value),
348+
// because signal forms skip value writes that don't change the value.
349+
if (!hasChanged && lastValueWasValid !== this._lastValueValid) {
350+
this._validatorOnChange();
348351
}
349352

350353
if (hasChanged) {

‎src/material/datepicker/datepicker.spec.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {createCloseScrollStrategy} from '@angular/cdk/overlay';
1313
import {_supportsShadowDom} from '@angular/cdk/platform';
1414
import {ScrollDispatcher} from '@angular/cdk/scrolling';
1515
import {
16+
clearElement,
1617
createKeyboardEvent,
1718
dispatchEvent,
1819
dispatchFakeEvent,
@@ -41,6 +42,7 @@ import {
4142
ReactiveFormsModule,
4243
Validator,
4344
} from '@angular/forms';
45+
import {FormField, form} from '@angular/forms/signals';
4446
import {By} from '@angular/platform-browser';
4547
import {Subject} from 'rxjs';
4648
import {MATERIAL_ANIMATIONS, MAT_DATE_LOCALE, provideNativeDateAdapter} from '../core';
@@ -1102,6 +1104,47 @@ describe('MatDatepicker', () => {
11021104
});
11031105
});
11041106

1107+
describe('datepicker with signal forms', () => {
1108+
let fixture: ComponentFixture<DatepickerWithSignalForms>;
1109+
let input: HTMLInputElement;
1110+
1111+
beforeEach(() => {
1112+
fixture = createComponent(DatepickerWithSignalForms, [provideNativeDateAdapter()]);
1113+
fixture.detectChanges();
1114+
input = fixture.nativeElement.querySelector('input');
1115+
});
1116+
1117+
function hasParseError() {
1118+
return fixture.componentInstance
1119+
.dateForm()
1120+
.errors()
1121+
.some(error => error.kind === 'matDatepickerParse');
1122+
}
1123+
1124+
it('should set the matDatepickerParse error when an invalid value is typed into an empty input', () => {
1125+
expect(hasParseError()).toBe(false);
1126+
1127+
typeInElement(input, 'foo');
1128+
fixture.detectChanges();
1129+
1130+
expect(hasParseError()).toBe(true);
1131+
});
1132+
1133+
it('should clear the matDatepickerParse error when an invalid value is erased', () => {
1134+
fixture.componentInstance.dateForm().value.set(new Date(2017, JAN, 1));
1135+
fixture.detectChanges();
1136+
expect(hasParseError()).toBe(false);
1137+
1138+
typeInElement(input, 'foo');
1139+
fixture.detectChanges();
1140+
expect(hasParseError()).toBe(true);
1141+
1142+
clearElement(input);
1143+
fixture.detectChanges();
1144+
expect(hasParseError()).toBe(false);
1145+
});
1146+
});
1147+
11051148
describe('datepicker with mat-datepicker-toggle', () => {
11061149
let fixture: ComponentFixture<DatepickerWithToggle>;
11071150
let testComponent: DatepickerWithToggle;
@@ -2515,6 +2558,17 @@ class DatepickerWithNgModel {
25152558
@ViewChild(MatDatepickerInput) datepickerInput!: MatDatepickerInput<Date>;
25162559
}
25172560

2561+
@Component({
2562+
template: `
2563+
<input [formField]="dateForm" [matDatepicker]="d">
2564+
<mat-datepicker #d></mat-datepicker>
2565+
`,
2566+
imports: [MatDatepickerInput, MatDatepicker, FormField],
2567+
})
2568+
class DatepickerWithSignalForms {
2569+
readonly dateForm = form(signal<Date | null>(null));
2570+
}
2571+
25182572
@Component({
25192573
template: `
25202574
<input [formControl]="formControl" [matDatepicker]="d">

0 commit comments

Comments
 (0)