From 02823c080be730d3c8cb7900b2449b660a5b0e08 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 4 Oct 2024 10:08:27 +0200 Subject: [PATCH] fix(cdk/stepper): reset submitted state when resetting stepper `CdkStepper` has a `reset` method that reset all the controls to their initial values, but that won't necessarily put the form into its initial state, because form controls also show errors on submit by default and `AbstractControl.reset` won't reset the submitted state. These changes add a call to reset all child forms to their unsubmitted state. Fixes #29781. --- src/cdk/stepper/stepper.ts | 26 ++++++++++++++++++++++++-- tools/public_api_guard/cdk/stepper.md | 5 ++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index ad0e2e10f89b..12e21b813350 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -33,7 +33,12 @@ import { numberAttribute, inject, } from '@angular/core'; -import {type AbstractControl} from '@angular/forms'; +import { + ControlContainer, + type AbstractControl, + type NgForm, + type FormGroupDirective, +} from '@angular/forms'; import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform'; import {Observable, of as observableOf, Subject} from 'rxjs'; import {startWith, takeUntil} from 'rxjs/operators'; @@ -101,7 +106,7 @@ export interface StepperOptions { @Component({ selector: 'cdk-step', exportAs: 'cdkStep', - template: '', + template: '', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, @@ -114,6 +119,19 @@ export class CdkStep implements OnChanges { /** Template for step label if it exists. */ @ContentChild(CdkStepLabel) stepLabel: CdkStepLabel; + /** Forms that have been projected into the step. */ + @ContentChildren( + // Note: we look for `ControlContainer` here, because both `NgForm` and `FormGroupDirective` + // provides themselves as such, but we don't want to have a concrete reference to both of + // the directives. The type is marked as `Partial` in case we run into a class that provides + // itself as `ControlContainer` but doesn't have the same interface as the directives. + ControlContainer, + { + descendants: true, + }, + ) + protected _childForms: QueryList> | undefined; + /** Template for step content. */ @ViewChild(TemplateRef, {static: true}) content: TemplateRef; @@ -205,6 +223,10 @@ export class CdkStep implements OnChanges { } if (this.stepControl) { + // Reset the forms since the default error state matchers will show errors on submit and we + // want the form to be back to its initial state (see #29781). Submitted state is on the + // individual directives, rather than the control, so we need to reset them ourselves. + this._childForms?.forEach(form => form.resetForm?.()); this.stepControl.reset(); } } diff --git a/tools/public_api_guard/cdk/stepper.md b/tools/public_api_guard/cdk/stepper.md index 74de9c630274..6c82b9b106b9 100644 --- a/tools/public_api_guard/cdk/stepper.md +++ b/tools/public_api_guard/cdk/stepper.md @@ -10,9 +10,11 @@ import { AfterViewInit } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; import { FocusableOption } from '@angular/cdk/a11y'; +import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; import * as i1 from '@angular/cdk/bidi'; import { InjectionToken } from '@angular/core'; +import { NgForm } from '@angular/forms'; import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { QueryList } from '@angular/core'; @@ -24,6 +26,7 @@ export class CdkStep implements OnChanges { constructor(...args: unknown[]); ariaLabel: string; ariaLabelledby: string; + protected _childForms: QueryList> | undefined; get completed(): boolean; set completed(value: boolean); // (undocumented) @@ -60,7 +63,7 @@ export class CdkStep implements OnChanges { // (undocumented) _stepper: CdkStepper; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; }