angular/components · error · Error

SelectionSet: index required when trackByFn is used.

Error message

SelectionSet: index required when trackByFn is used.

What it means

SelectionSet._getTrackedByValue throws when a trackByFn was provided but the SelectableWithIndex passed in has a null/undefined index. With a custom track-by function, identity of each item is computed as trackByFn(index, value), so an index is mandatory. isSelected, select, and deselect all route through this check. Dev-mode only.

Source

Thrown at src/cdk-experimental/selection/selection-set.ts:121

    const after = this._getCurrentSelection();
    this.changed.next({before, after});
  }

  private _markSelected(key: T | ReturnType<TrackByFunction<T>>, toSelect: SelectableWithIndex<T>) {
    this._selectionMap.set(key, toSelect);
  }

  private _markDeselected(key: T | ReturnType<TrackByFunction<T>>) {
    this._selectionMap.delete(key);
  }

  private _getTrackedByValue(select: SelectableWithIndex<T>) {
    if (!this._trackByFn) {
      return select.value;
    }

    if (select.index == null && (typeof ngDevMode === 'undefined' || ngDevMode)) {
      throw Error('SelectionSet: index required when trackByFn is used.');
    }

    return this._trackByFn(select.index!, select.value);
  }

  private _getCurrentSelection(): SelectableWithIndex<T>[] {
    return Array.from(this._selectionMap.values());
  }
}

View on GitHub (pinned to 0411926e7d)

Solutions

  1. Pass the index with every call: select({ value, index }), deselect({ value, index }), isSelected({ value, index }).
  2. If items have no meaningful index, remove the trackByFn so identity falls back to the value itself.
  3. Make index non-optional at call sites via types so TypeScript catches omissions.

Example fix

// before
set.select({ value: item.value });

// after
set.select({ value: item.value, index: item.index });
Defensive patterns

Strategy: type-guard

Validate before calling

function hasIndex<T>(s: SelectableWithIndex<T>): boolean {
  return !trackByInUse || s.index != null;
}
if (!hasIndex(sel)) throw new Error('index required when trackByFn is used');

Type guard

function hasSelectionIndex<T>(s: SelectableWithIndex<T>): s is SelectableWithIndex<T> & { index: number } {
  return s.index != null;
}

Try / catch

try {
  set.select(sel);
} catch (e) {
  if ((e as Error).message.includes('index required')) {
    throw new Error(`Callers must supply index for ${String(sel.value)} when trackByFn is set`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling isSelected(value), select({value}), or deselect({value}) — omitting index — on a SelectionSet configured with a trackByFn. Also calling CdkSelection wrappers that forward {value, index: undefined}.

Common situations: Migrating from value-only tracking to trackByFn without updating every call site; storing Selectable objects built before trackBy was added; template code calling select with only a value binding.

Related errors


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