Skip to content

Commit 6bdea0b

Browse files
authored
fix(cdk/stepper): resolve null pointer errors (#30944)
The stepper initializes its key manager in `ngAfterViewInit`, but uses it in methods that can technically be called before it is available. These changes add some null checks and update the type to avoid throwing errors. Fixes #30864.
1 parent 96fa9fc commit 6bdea0b

File tree

1 file changed

+14
-7
lines changed

1 file changed

+14
-7
lines changed

src/cdk/stepper/stepper.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
261261
protected readonly _destroyed = new Subject<void>();
262262

263263
/** Used for managing keyboard focus. */
264-
private _keyManager: FocusKeyManager<FocusableOption>;
264+
private _keyManager: FocusKeyManager<FocusableOption> | undefined;
265265

266266
/** Full list of steps inside the stepper, including inside nested steppers. */
267267
@ContentChildren(CdkStep, {descendants: true}) _steps: QueryList<CdkStep>;
@@ -384,9 +384,14 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
384384
.withHomeAndEnd()
385385
.withVerticalOrientation(this._orientation === 'vertical');
386386

387+
// The selected index may have changed between when the component was created and when the
388+
// key manager was initialized. Use `updateActiveItem` so it's correct, but it doesn't steal
389+
// away focus from the user.
390+
this._keyManager.updateActiveItem(this.selectedIndex);
391+
387392
(this._dir ? (this._dir.change as Observable<Direction>) : observableOf<Direction>())
388393
.pipe(startWith(this._layoutDirection()), takeUntil(this._destroyed))
389-
.subscribe(direction => this._keyManager.withHorizontalOrientation(direction));
394+
.subscribe(direction => this._keyManager?.withHorizontalOrientation(direction));
390395

391396
this._keyManager.updateActiveItem(this._selectedIndex);
392397

@@ -526,9 +531,11 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
526531
// lost when the active step content is hidden. We can't be more granular with the check
527532
// (e.g. checking whether focus is inside the active step), because we don't have a
528533
// reference to the elements that are rendering out the content.
529-
this._containsFocus()
530-
? this._keyManager.setActiveItem(newIndex)
531-
: this._keyManager.updateActiveItem(newIndex);
534+
if (this._keyManager) {
535+
this._containsFocus()
536+
? this._keyManager.setActiveItem(newIndex)
537+
: this._keyManager.updateActiveItem(newIndex);
538+
}
532539

533540
this._selectedIndex = newIndex;
534541
this.selectedIndexChange.emit(this._selectedIndex);
@@ -541,14 +548,14 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
541548
const manager = this._keyManager;
542549

543550
if (
544-
manager.activeItemIndex != null &&
551+
manager?.activeItemIndex != null &&
545552
!hasModifier &&
546553
(keyCode === SPACE || keyCode === ENTER)
547554
) {
548555
this.selectedIndex = manager.activeItemIndex;
549556
event.preventDefault();
550557
} else {
551-
manager.setFocusOrigin('keyboard').onKeydown(event);
558+
manager?.setFocusOrigin('keyboard').onKeydown(event);
552559
}
553560
}
554561

0 commit comments

Comments
 (0)