angular/components · critical

A valid data source must be provided.

Error message

A valid data source must be provided.

What it means

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).

Source

Thrown at src/cdk/tree/tree.ts:389

  /** Set up a subscription for the data provided by the data source. */
  private _subscribeToDataChanges() {
    if (this._dataSubscription) {
      return;
    }

    let dataStream: Observable<readonly T[]> | undefined;

    if (isDataSource(this._dataSource)) {
      dataStream = this._dataSource.connect(this);
    } else if (isObservable(this._dataSource)) {
      dataStream = this._dataSource;
    } else if (Array.isArray(this._dataSource)) {
      dataStream = observableOf(this._dataSource);
    }

    if (!dataStream) {
      if (typeof ngDevMode === 'undefined' || ngDevMode) {
        throw getTreeNoValidDataSourceError();
      }
      return;
    }

    this._dataSubscription = this._getRenderData(dataStream)
      .pipe(takeUntil(this._onDestroy))
      .subscribe(renderingData => {
        this._renderDataChanges(renderingData);
      });
  }

  /** Given an Observable containing a stream of the raw data, returns an Observable containing the RenderingData */
  private _getRenderData(dataStream: Observable<readonly T[]>): Observable<RenderingData<T>> {
    const expansionModel = this._getExpansionModel();
    return combineLatest([
      dataStream,
      this._nodeType,
      // We don't use the expansion data directly, however we add it here to essentially

View on GitHub (pinned to 0411926e7d)

Solutions

  1. Bind a valid [dataSource] input: an array, Observable<T[]>, or DataSource<T> instance.
  2. 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(...))).
  3. If using treeControl-based flat/nested trees, set treeControl.dataSource to the data source instance instead of leaving everything undefined.
  4. Ensure the binding name is correct ([dataSource], not [data] or [source]) and that the value is not a plain non-stream object.

Example fix

// before
nodeSource: TreeNode[] | undefined;
ngOnInit() { this.http.get('/api/tree').subscribe(d => this.nodeSource = d); }
<cdk-tree [dataSource]="nodeSource">...

// after
nodeSource: TreeNode[] = [];
ngOnInit() { this.http.get<TreeNode[]>('/api/tree').subscribe(d => this.nodeSource = d); }
<cdk-tree [dataSource]="nodeSource">...
Defensive patterns

Strategy: validation

Validate before calling

function hasValidDataSource(el: HTMLElement): boolean {
  const tree = (el as any).__cdkTree ?? lookupTreeInstance(el);
  return !!tree && (
    Array.isArray(tree.dataSource) ||
    tree.dataSource instanceof CdkTreeDataSource ||
    tree.dataSource instanceof Observable ||
    !!tree.treeControl?.dataSource
  );
}
// template guard: *ngIf="nodes$ | async as nodes" so [dataSource] is never undefined

Type guard

function isValidDataSource<T>(ds: unknown): ds is T[] | Observable<T[]> | DataSource<T> {
  return Array.isArray(ds) || ds instanceof Observable || (!!ds && typeof (ds as any).connect === 'function');
}

Try / catch

try {
  this.zone.onUnstable.pipe(first()).subscribe(() => this.tree.renderNodeChanges(this.tree.dataSource as any[]));
} catch (e) {
  if (e && String(e.message).includes('valid data source')) {
    this.tree.dataSource = this.tree.dataSource ?? [];
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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