angular/components · error · Error

KeyManager items in typeahead mode must implement the `getLa

Error message

KeyManager items in typeahead mode must implement the `getLabel` method.

What it means

When ListKeyManager typeahead mode is enabled, the manager needs each item's label to match typed keystrokes. The typeahead implementation validates in dev mode that every item implements getLabel(); if any item lacks it, the constructor throws.

Source

Thrown at src/cdk/a11y/key-manager/typeahead.ts:56

  private readonly _selectedItem = new Subject<T>();
  readonly selectedItem: Observable<T> = this._selectedItem;

  constructor(initialItems: readonly T[], config?: TypeaheadConfig<T>) {
    const typeAheadInterval =
      typeof config?.debounceInterval === 'number'
        ? config.debounceInterval
        : DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL_MS;

    if (config?.skipPredicate) {
      this._skipPredicateFn = config.skipPredicate;
    }

    if (
      (typeof ngDevMode === 'undefined' || ngDevMode) &&
      initialItems.length &&
      initialItems.some(item => typeof item.getLabel !== 'function')
    ) {
      throw new Error('KeyManager items in typeahead mode must implement the `getLabel` method.');
    }

    this.setItems(initialItems);
    this._setupKeyHandler(typeAheadInterval);
  }

  destroy() {
    this._pressedLetters = [];
    this._letterKeyStream.complete();
    this._selectedItem.complete();
  }

  setCurrentSelectedItemIndex(index: number) {
    this._selectedItemIndex = index;
  }

  setItems(items: readonly T[]) {
    this._items = items;

View on GitHub (pinned to 0411926e7d)

Solutions

  1. Implement getLabel(): string on every item type passed to ListKeyManager.
  2. Use a proper interface (e.g. interface ListItem { getLabel(): string; disabled?: boolean }) so TypeScript enforces it.
  3. If typeahead isn't needed, remove withTypeAhead() from the config.
  4. If items are added later, note the check runs on initial items — still implement getLabel to avoid runtime failures on navigation.

Example fix

// before
items = [{id: 1, name: 'Foo'}];
new ListKeyManager(items as any).withTypeAhead();
// after
interface Item { getLabel(): string; }
items: Item[] = [{id: 1, name: 'Foo', getLabel() { return this.name; }}];
new ListKeyManager(items).withTypeAhead();
Defensive patterns

Strategy: type-guard

Validate before calling

const usable = initialItems.every((i: any) => typeof i.getLabel === 'function');
if (!usable) throw new Error('All key manager items must implement getLabel');

Type guard

function hasGetLabel(item: unknown): item is {getLabel(): string} {
  return typeof item === 'object' && item !== null &&
    typeof (item as {getLabel?: unknown}).getLabel === 'function';
}

Try / catch

try {
  manager = new ListKeyManager(items).withTypeAhead();
} catch (e) {
  if ((e as Error).message.includes('getLabel')) {
    manager = new ListKeyManager(items.map(withDefaultLabel)).withTypeAhead();
  } else throw e;
}

Prevention

When it happens

Trigger: Constructing ListKeyManager with withTypeAhead() where items are objects/classes that don't implement getLabel(), and at least one item exists at construction time.

Common situations: Switching items from strings (which typeahead can't label either) or from DOM elements to plain objects without adding getLabel; custom item types after refactoring; passing a signal/QueryList of untyped items where TS structural checks were bypassed with `as any`.

Related errors


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/2c7a2f8341216f23. Report an issue: GitHub.