{"record":{"id":"24810e71085f61de","repo":"angular/components","slug":"a-valid-data-source-must-be-provided","errorCode":null,"errorMessage":"A valid data source must be provided.","messagePattern":"A valid data source must be provided\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/cdk/tree/tree.ts","lineNumber":389,"sourceCode":"  /** Set up a subscription for the data provided by the data source. */\n  private _subscribeToDataChanges() {\n    if (this._dataSubscription) {\n      return;\n    }\n\n    let dataStream: Observable<readonly T[]> | undefined;\n\n    if (isDataSource(this._dataSource)) {\n      dataStream = this._dataSource.connect(this);\n    } else if (isObservable(this._dataSource)) {\n      dataStream = this._dataSource;\n    } else if (Array.isArray(this._dataSource)) {\n      dataStream = observableOf(this._dataSource);\n    }\n\n    if (!dataStream) {\n      if (typeof ngDevMode === 'undefined' || ngDevMode) {\n        throw getTreeNoValidDataSourceError();\n      }\n      return;\n    }\n\n    this._dataSubscription = this._getRenderData(dataStream)\n      .pipe(takeUntil(this._onDestroy))\n      .subscribe(renderingData => {\n        this._renderDataChanges(renderingData);\n      });\n  }\n\n  /** Given an Observable containing a stream of the raw data, returns an Observable containing the RenderingData */\n  private _getRenderData(dataStream: Observable<readonly T[]>): Observable<RenderingData<T>> {\n    const expansionModel = this._getExpansionModel();\n    return combineLatest([\n      dataStream,\n      this._nodeType,\n      // We don't use the expansion data directly, however we add it here to essentially","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk/tree/tree.ts#L371-L407","documentation":"The CDK tree requires a way to obtain its data. After ngAfterContentChecked runs _subscribeToDataChanges, it resolves the stream from [dataSource], treeControl, levelAccessor/childrenAccessor via _switchDataSource. If none of [dataSource] as array/Observable/DataSource, treeControl.dataSource, or an accessor yields a stream, the tree cannot render and getTreeNoValidDataSourceError() is thrown (dev mode only; in prod it silently returns).","triggerScenarios":"Rendering <cdk-tree> (or mat-tree) with no [dataSource] input bound; binding dataSource only after a delayed HTTP response so the first _subscribeToDataChanges pass runs while dataSource is still undefined without ever being set; assigning a non-array, non-DataSource, non-Observable value; a custom tree missing both treeControl and dataSource.","commonSituations":"Async data fetched over HTTP where [dataSource] is initialized to undefined/null and never set to an empty array; forgetting the [dataSource] attribute while only providing treeControl; renaming the input in a refactor; conditional rendering that strips the binding; mat-tree nested-tree migration where treeControl was expected to supply data.","solutions":["Bind a valid [dataSource] input: an array, Observable<T[]>, or DataSource<T> instance.","If data loads asynchronously, initialize with [] (e.g. dataSource: MyNode[] = []) and update it when the response arrives, or pass an Observable that starts empty (of([]).pipe(switchMap(...))).","If using treeControl-based flat/nested trees, set treeControl.dataSource to the data source instance instead of leaving everything undefined.","Ensure the binding name is correct ([dataSource], not [data] or [source]) and that the value is not a plain non-stream object."],"exampleFix":"// before\nnodeSource: TreeNode[] | undefined;\nngOnInit() { this.http.get('/api/tree').subscribe(d => this.nodeSource = d); }\n<cdk-tree [dataSource]=\"nodeSource\">...\n\n// after\nnodeSource: TreeNode[] = [];\nngOnInit() { this.http.get<TreeNode[]>('/api/tree').subscribe(d => this.nodeSource = d); }\n<cdk-tree [dataSource]=\"nodeSource\">...","handlingStrategy":"validation","validationCode":"function hasValidDataSource(el: HTMLElement): boolean {\n  const tree = (el as any).__cdkTree ?? lookupTreeInstance(el);\n  return !!tree && (\n    Array.isArray(tree.dataSource) ||\n    tree.dataSource instanceof CdkTreeDataSource ||\n    tree.dataSource instanceof Observable ||\n    !!tree.treeControl?.dataSource\n  );\n}\n// template guard: *ngIf=\"nodes$ | async as nodes\" so [dataSource] is never undefined","typeGuard":"function isValidDataSource<T>(ds: unknown): ds is T[] | Observable<T[]> | DataSource<T> {\n  return Array.isArray(ds) || ds instanceof Observable || (!!ds && typeof (ds as any).connect === 'function');\n}","tryCatchPattern":"try {\n  this.zone.onUnstable.pipe(first()).subscribe(() => this.tree.renderNodeChanges(this.tree.dataSource as any[]));\n} catch (e) {\n  if (e && String(e.message).includes('valid data source')) {\n    this.tree.dataSource = this.tree.dataSource ?? [];\n  } else { throw e; }\n}","preventionTips":["Always initialize [dataSource] to an empty array or of([]), never undefined/null.","Assign fetched data into the same array reference's contents or reassign only after the tree exists.","Add a component-level check in ngOnInit: if (!this.dataSource) throw new Error('tree dataSource missing').","Use strict template type checking so binding a non-stream type fails at compile time."],"tags":["angular","cdk-tree","data-source","missing-input"],"backgroundTag":"missing-data-source","analyzedSha":"0411926e7d8ae06b32236ec1048a888cfad5abf2","analyzedAt":"2026-08-31T11:58:23.400Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}