diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index cac528d64a4f..da2b61dd018c 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -293,12 +293,11 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { if (this.steps && this._steps) { // Ensure that the index can't be out of bounds. - if ((newIndex < 0 || newIndex > this.steps.length - 1) && - (typeof ngDevMode === 'undefined' || ngDevMode)) { + if (!this._isValidIndex(index) && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw Error('cdkStepper: Cannot assign out-of-bounds value to `selectedIndex`.'); } - if (this._selectedIndex != newIndex && !this._anyControlsInvalidOrPending(newIndex) && + if (this._selectedIndex !== newIndex && !this._anyControlsInvalidOrPending(newIndex) && (newIndex >= this._selectedIndex || this.steps.toArray()[newIndex].editable)) { this._updateSelectedItemIndex(index); } @@ -365,6 +364,13 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { this._selectedIndex = Math.max(this._selectedIndex - 1, 0); } }); + + // The logic which asserts that the selected index is within bounds doesn't run before the + // steps are initialized, because we don't how many steps there are yet so we may have an + // invalid index on init. If that's the case, auto-correct to the default so we don't throw. + if (!this._isValidIndex(this._selectedIndex)) { + this._selectedIndex = 0; + } } ngOnDestroy() { @@ -525,6 +531,11 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { return stepperElement === focusedElement || stepperElement.contains(focusedElement); } + /** Checks whether the passed-in index is a valid step index. */ + private _isValidIndex(index: number): boolean { + return index > -1 && (!this.steps || index < this.steps.length); + } + static ngAcceptInputType_editable: BooleanInput; static ngAcceptInputType_optional: BooleanInput; static ngAcceptInputType_completed: BooleanInput; diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index 04377c66c563..8c05870055c9 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -29,6 +29,7 @@ import { Provider, ViewChildren, QueryList, + ViewChild, } from '@angular/core'; import {ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing'; import { @@ -1270,6 +1271,24 @@ describe('MatStepper', () => { expect(steppers[0].steps.length).toBe(3); expect(steppers[1].steps.length).toBe(2); }); + + it('should not throw when trying to change steps after initializing to an out-of-bounds index', + () => { + const fixture = createComponent(StepperWithStaticOutOfBoundsIndex); + fixture.detectChanges(); + const stepper = fixture.componentInstance.stepper; + + expect(stepper.selectedIndex).toBe(0); + expect(stepper.selected).toBeTruthy(); + + expect(() => { + stepper.selectedIndex = 1; + fixture.detectChanges(); + }).not.toThrow(); + + expect(stepper.selectedIndex).toBe(1); + expect(stepper.selected).toBeTruthy(); + }); }); /** Asserts that keyboard interaction works correctly. */ @@ -1780,3 +1799,17 @@ class StepperWithNgIf { class NestedSteppers { @ViewChildren(MatStepper) steppers: QueryList; } + + +@Component({ + template: ` + + Content 1 + Content 2 + Content 3 + + ` +}) +class StepperWithStaticOutOfBoundsIndex { + @ViewChild(MatStepper) stepper: MatStepper; +}