{"record":{"id":"8a90f269efb525c9","repo":"angular/components","slug":"selectionset-index-required-when-trackbyfn-is-use","errorCode":null,"errorMessage":"SelectionSet: index required when trackByFn is used.","messagePattern":"SelectionSet: index required when trackByFn is used\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cdk-experimental/selection/selection-set.ts","lineNumber":121,"sourceCode":"    const after = this._getCurrentSelection();\n    this.changed.next({before, after});\n  }\n\n  private _markSelected(key: T | ReturnType<TrackByFunction<T>>, toSelect: SelectableWithIndex<T>) {\n    this._selectionMap.set(key, toSelect);\n  }\n\n  private _markDeselected(key: T | ReturnType<TrackByFunction<T>>) {\n    this._selectionMap.delete(key);\n  }\n\n  private _getTrackedByValue(select: SelectableWithIndex<T>) {\n    if (!this._trackByFn) {\n      return select.value;\n    }\n\n    if (select.index == null && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n      throw Error('SelectionSet: index required when trackByFn is used.');\n    }\n\n    return this._trackByFn(select.index!, select.value);\n  }\n\n  private _getCurrentSelection(): SelectableWithIndex<T>[] {\n    return Array.from(this._selectionMap.values());\n  }\n}\n","sourceCodeStart":103,"sourceCodeEnd":131,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk-experimental/selection/selection-set.ts#L103-L131","documentation":"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.","triggerScenarios":"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}.","commonSituations":"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.","solutions":["Pass the index with every call: select({ value, index }), deselect({ value, index }), isSelected({ value, index }).","If items have no meaningful index, remove the trackByFn so identity falls back to the value itself.","Make index non-optional at call sites via types so TypeScript catches omissions."],"exampleFix":"// before\nset.select({ value: item.value });\n\n// after\nset.select({ value: item.value, index: item.index });","handlingStrategy":"type-guard","validationCode":"function hasIndex<T>(s: SelectableWithIndex<T>): boolean {\n  return !trackByInUse || s.index != null;\n}\nif (!hasIndex(sel)) throw new Error('index required when trackByFn is used');","typeGuard":"function hasSelectionIndex<T>(s: SelectableWithIndex<T>): s is SelectableWithIndex<T> & { index: number } {\n  return s.index != null;\n}","tryCatchPattern":"try {\n  set.select(sel);\n} catch (e) {\n  if ((e as Error).message.includes('index required')) {\n    throw new Error(`Callers must supply index for ${String(sel.value)} when trackByFn is set`);\n  }\n  throw e;\n}","preventionTips":["Type selectable items with a non-optional index whenever trackByFn is configured.","Grep for select(/deselect(/isSelected( call sites when adding a trackByFn.","Prefer value-only tracking when items lack stable indices."],"tags":["angular","cdk","selection","trackby","dev-mode-assertion"],"backgroundTag":"trackby-index-required","analyzedSha":"0411926e7d8ae06b32236ec1048a888cfad5abf2","analyzedAt":"2026-08-31T11:58:23.400Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}