angular/components · error

More than one of tree control, levelAccessor, or childrenAcc

Error message

More than one of tree control, levelAccessor, or childrenAccessor were provided.

What it means

_checkTreeControlUsage requires exactly one traversal mechanism. If the tree receives more than one of treeControl, levelAccessor, or childrenAccessor simultaneously, the tree cannot decide how to compute levels/children and getMultipleTreeControlsError() is thrown.

Source

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

    if (typeof ngDevMode === 'undefined' || ngDevMode) {
      // Verify that Tree follows API contract of using one of TreeControl, levelAccessor or
      // childrenAccessor. Throw an appropriate error if contract is not met.
      let numTreeControls = 0;

      if (this.treeControl) {
        numTreeControls++;
      }
      if (this.levelAccessor) {
        numTreeControls++;
      }
      if (this.childrenAccessor) {
        numTreeControls++;
      }

      if (!numTreeControls) {
        throw getTreeControlMissingError();
      } else if (numTreeControls > 1) {
        throw getMultipleTreeControlsError();
      }
    }
  }

  /** Check for changes made in the data and render each change (node added/removed/moved). */
  renderNodeChanges(
    data: readonly T[],
    dataDiffer: IterableDiffer<T> = this._dataDiffer,
    viewContainer: ViewContainerRef = this._nodeOutlet.viewContainer,
    parentData?: T,
  ) {
    const changes = dataDiffer.diff(data);

    // Some tree consumers expect change detection to propagate to nodes
    // even when the array itself hasn't changed; we explicitly detect changes
    // anyways in order for nodes to update their data.
    //
    // However, if change detection is called while the component's view is

View on GitHub (pinned to 0411926e7d)

Solutions

  1. Remove all but one of [treeControl], [levelAccessor], [childrenAccessor] from the tree element.
  2. If moving to the accessor API, delete the [treeControl] binding and use [levelAccessor] or [childrenAccessor].
  3. If the bindings come from a shared template/component, gate them with *ngIf or conditional property binding so only one is ever set.
  4. Search the template for duplicate bindings spread across the element or inherited via ngTemplateOutlet context.

Example fix

// before
<cdk-tree [dataSource]="ds" [treeControl]="treeControl" [levelAccessor]="node => node.level">...

// after
<cdk-tree [dataSource]="ds" [levelAccessor]="node => node.level">...
Defensive patterns

Strategy: validation

Validate before calling

const provided = [
  (tree as any).treeControl,
  (tree as any).levelAccessor,
  (tree as any).childrenAccessor
].filter(Boolean).length;
if (provided > 1) console.error('cdk-tree accepts only ONE of treeControl, levelAccessor, childrenAccessor');

Type guard

function hasExactlyOneTraversal(t: { treeControl?: unknown; levelAccessor?: unknown; childrenAccessor?: unknown }): boolean {
  return ((!!t.treeControl ? 1 : 0) + (!!t.levelAccessor ? 1 : 0) + (!!t.childrenAccessor ? 1 : 0)) === 1;
}

Try / catch

try {
  this.tree.ngOnInit();
} catch (e) {
  if (e && String(e.message).includes('More than one of tree control')) {
    this.tree.levelAccessor = undefined; // drop the redundant accessor
  } else { throw e; }
}

Prevention

When it happens

Trigger: ngOnInit with bindings like <cdk-tree [treeControl]="ctrl" [levelAccessor]="fn">, or [levelAccessor] together with [childrenAccessor], or [treeControl] plus [childrenAccessor].

Common situations: Migrating from treeControl-based code to the newer accessor API (CDK 13+) and adding accessors without removing the old treeControl; merging template snippets from two examples; spreading a dynamic attributes object that contains both configs; an abstract tree base class that always sets treeControl while subclasses add accessors.

Related errors


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