Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 25 additions & 53 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ import {
numberAttribute,
inject,
} from '@angular/core';
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';
Expand Down Expand Up @@ -100,7 +106,7 @@ export interface StepperOptions {
@Component({
selector: 'cdk-step',
exportAs: 'cdkStep',
template: '<ng-template><ng-content></ng-content></ng-template>',
template: '<ng-template><ng-content/></ng-template>',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
Expand All @@ -113,11 +119,24 @@ 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<Partial<NgForm | FormGroupDirective>> | undefined;

/** Template for step content. */
@ViewChild(TemplateRef, {static: true}) content: TemplateRef<any>;

/** The top level abstract control of the step. */
@Input() stepControl: AbstractControlLike;
@Input() stepControl: AbstractControl;

/** Whether user has attempted to move away from the step. */
interacted = false;
Expand Down Expand Up @@ -204,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();
}
}
Expand Down Expand Up @@ -556,54 +579,3 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
return index > -1 && (!this.steps || index < this.steps.length);
}
}

/**
* Simplified representation of an "AbstractControl" from @angular/forms.
* Used to avoid having to bring in @angular/forms for a single optional interface.
* @docs-private
*/
interface AbstractControlLike {
asyncValidator: ((control: any) => any) | null;
dirty: boolean;
disabled: boolean;
enabled: boolean;
errors: {[key: string]: any} | null;
invalid: boolean;
parent: any;
pending: boolean;
pristine: boolean;
root: AbstractControlLike;
status: string;
readonly statusChanges: Observable<any>;
touched: boolean;
untouched: boolean;
updateOn: any;
valid: boolean;
validator: ((control: any) => any) | null;
value: any;
readonly valueChanges: Observable<any>;
clearAsyncValidators(): void;
clearValidators(): void;
disable(opts?: any): void;
enable(opts?: any): void;
get(path: (string | number)[] | string): AbstractControlLike | null;
getError(errorCode: string, path?: (string | number)[] | string): any;
hasError(errorCode: string, path?: (string | number)[] | string): boolean;
markAllAsTouched(): void;
markAsDirty(opts?: any): void;
markAsPending(opts?: any): void;
markAsPristine(opts?: any): void;
markAsTouched(opts?: any): void;
markAsUntouched(opts?: any): void;
patchValue(value: any, options?: Object): void;
reset(value?: any, options?: Object): void;
setAsyncValidators(newValidator: (control: any) => any | ((control: any) => any)[] | null): void;
setErrors(errors: {[key: string]: any} | null, opts?: any): void;
setParent(parent: any): void;
setValidators(newValidator: (control: any) => any | ((control: any) => any)[] | null): void;
setValue(value: any, options?: Object): void;
updateValueAndValidity(opts?: any): void;
patchValue(value: any, options?: any): void;
reset(formState?: any, options?: any): void;
setValue(value: any, options?: any): void;
}
9 changes: 6 additions & 3 deletions tools/public_api_guard/cdk/stepper.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@

```ts

import { AbstractControl } from '@angular/forms';
import { AfterContentInit } from '@angular/core';
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 { Observable } from 'rxjs';
import { NgForm } from '@angular/forms';
import { OnChanges } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { QueryList } from '@angular/core';
Expand All @@ -24,6 +26,7 @@ export class CdkStep implements OnChanges {
constructor(...args: unknown[]);
ariaLabel: string;
ariaLabelledby: string;
protected _childForms: QueryList<Partial<NgForm | FormGroupDirective>> | undefined;
get completed(): boolean;
set completed(value: boolean);
// (undocumented)
Expand Down Expand Up @@ -55,12 +58,12 @@ export class CdkStep implements OnChanges {
select(): void;
_showError(): boolean;
state: StepState;
stepControl: AbstractControlLike;
stepControl: AbstractControl;
stepLabel: CdkStepLabel;
// (undocumented)
_stepper: CdkStepper;
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<CdkStep, "cdk-step", ["cdkStep"], { "stepControl": { "alias": "stepControl"; "required": false; }; "label": { "alias": "label"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "state": { "alias": "state"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "optional": { "alias": "optional"; "required": false; }; "completed": { "alias": "completed"; "required": false; }; "hasError": { "alias": "hasError"; "required": false; }; }, { "interactedStream": "interacted"; }, ["stepLabel"], ["*"], true, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<CdkStep, "cdk-step", ["cdkStep"], { "stepControl": { "alias": "stepControl"; "required": false; }; "label": { "alias": "label"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "state": { "alias": "state"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "optional": { "alias": "optional"; "required": false; }; "completed": { "alias": "completed"; "required": false; }; "hasError": { "alias": "hasError"; "required": false; }; }, { "interactedStream": "interacted"; }, ["stepLabel", "_childForms"], ["*"], true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<CdkStep, never>;
}
Expand Down
Loading