Documentation Index
Fetch the complete documentation index at: https://mintlify.com/angular/components/llms.txt
Use this file to discover all available pages before exploring further.
MatTree
The mat-tree provides a Material Design styled tree component for displaying hierarchical data with expandable and collapsible nodes.
Basic Usage
import { MatTreeModule } from '@angular/material/tree';
import { NestedTreeControl } from '@angular/cdk/tree';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
interface FoodNode {
name: string;
children?: FoodNode[];
}
const TREE_DATA: FoodNode[] = [
{
name: 'Fruit',
children: [{name: 'Apple'}, {name: 'Banana'}, {name: 'Orange'}],
},
{
name: 'Vegetables',
children: [{name: 'Carrot'}, {name: 'Lettuce'}],
},
];
@Component({
selector: 'tree-example',
imports: [MatTreeModule, MatIconModule, MatButtonModule],
template: `
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
<button mat-icon-button disabled></button>
{{ node.name }}
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
<div class="mat-tree-node">
<button mat-icon-button matTreeNodeToggle>
<mat-icon>
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
{{ node.name }}
</div>
<div [class.tree-invisible]="!treeControl.isExpanded(node)" role="group">
<ng-container matTreeNodeOutlet></ng-container>
</div>
</mat-nested-tree-node>
</mat-tree>
`,
styles: [`
.tree-invisible {
display: none;
}
`]
})
export class TreeExample {
treeControl = new NestedTreeControl<FoodNode>(node => node.children);
dataSource = TREE_DATA;
hasChild = (_: number, node: FoodNode) => !!node.children && node.children.length > 0;
}
API Reference
MatTree
Selector: mat-tree
Extends: CdkTree
| Name | Type | Description |
|---|
dataSource | DataSource<T> | Observable<T[]> | T[] | Data source for the tree |
treeControl | TreeControl<T> | Tree control for managing tree state |
MatTreeNode
Directive: mat-tree-node
Represents a node in the tree.
| Name | Type | Default | Description |
|---|
disabled | boolean | false | Whether the node is disabled |
tabIndex | number | 0 | Tabindex of the node |
MatNestedTreeNode
Directive: mat-nested-tree-node
Represents a nested node in the tree with children.
| Name | Type | Default | Description |
|---|
disabled | boolean | false | Whether the node is disabled |
tabIndex | number | 0 | Tabindex of the node |
MatTreeNodeDef
Directive: [matTreeNodeDef]
Defines the template for tree nodes:
<mat-tree-node *matTreeNodeDef="let node">{{ node.name }}</mat-tree-node>
MatTreeNodeToggle
Directive: matTreeNodeToggle
Toggle for expanding/collapsing tree nodes.
MatTreeNodeOutlet
Directive: matTreeNodeOutlet
Outlet for nested tree nodes.
MatTreeNodePadding
Directive: matTreeNodePadding
Adds indentation padding to tree nodes based on level.
Examples
Flat Tree
import { FlatTreeControl } from '@angular/cdk/tree';
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
interface FoodNode {
name: string;
children?: FoodNode[];
}
interface FlatNode {
expandable: boolean;
name: string;
level: number;
}
@Component({
template: `
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<button mat-icon-button disabled></button>
{{ node.name }}
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'Toggle ' + node.name">
<mat-icon>
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
{{ node.name }}
</mat-tree-node>
</mat-tree>
`
})
export class FlatTreeExample {
private transformer = (node: FoodNode, level: number): FlatNode => {
return {
expandable: !!node.children && node.children.length > 0,
name: node.name,
level: level,
};
};
treeControl = new FlatTreeControl<FlatNode>(
node => node.level,
node => node.expandable,
);
treeFlattener = new MatTreeFlattener(
this.transformer,
node => node.level,
node => node.expandable,
node => node.children,
);
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
constructor() {
this.dataSource.data = TREE_DATA;
}
hasChild = (_: number, node: FlatNode) => node.expandable;
}
Checkable Tree
import { SelectionModel } from '@angular/cdk/collections';
import { MatCheckboxModule } from '@angular/material/checkbox';
@Component({
imports: [MatTreeModule, MatCheckboxModule, MatIconModule, MatButtonModule],
template: `
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<button mat-icon-button disabled></button>
<mat-checkbox [checked]="checklistSelection.isSelected(node)"
(change)="todoItemSelectionToggle(node)">
{{ node.name }}
</mat-checkbox>
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
<button mat-icon-button matTreeNodeToggle>
<mat-icon>
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
<mat-checkbox [checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)">
{{ node.name }}
</mat-checkbox>
</mat-tree-node>
</mat-tree>
`
})
export class CheckableTreeExample {
treeControl = new FlatTreeControl<FlatNode>(
node => node.level,
node => node.expandable,
);
checklistSelection = new SelectionModel<FlatNode>(true);
// ... implementation of selection methods
todoItemSelectionToggle(node: FlatNode): void {
this.checklistSelection.toggle(node);
const descendants = this.treeControl.getDescendants(node);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);
}
descendantsAllSelected(node: FlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
return descendants.length > 0 &&
descendants.every(child => this.checklistSelection.isSelected(child));
}
descendantsPartiallySelected(node: FlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
const result = descendants.some(child => this.checklistSelection.isSelected(child));
return result && !this.descendantsAllSelected(node);
}
}
Dynamic Tree
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable()
export class DynamicDatabase {
dataChange = new BehaviorSubject<DynamicFlatNode[]>([]);
get data(): DynamicFlatNode[] {
return this.dataChange.value;
}
initialize() {
const data = this.buildFileTree(TREE_DATA, 0);
this.dataChange.next(data);
}
buildFileTree(obj: {[key: string]: any}, level: number): DynamicFlatNode[] {
return Object.keys(obj).reduce<DynamicFlatNode[]>((accumulator, key) => {
const value = obj[key];
const node = new DynamicFlatNode();
node.item = key;
if (value != null) {
if (typeof value === 'object') {
node.children = this.buildFileTree(value, level + 1);
} else {
node.item = value;
}
}
return accumulator.concat(node);
}, []);
}
}
@Component({
template: `
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<button mat-icon-button disabled></button>
{{ node.item }}
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
<button mat-icon-button
[attr.aria-label]="'Toggle ' + node.item"
(click)="loadChildren(node)"
matTreeNodeToggle>
<mat-icon>
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
{{ node.item }}
<mat-progress-bar *ngIf="node.isLoading" mode="indeterminate"></mat-progress-bar>
</mat-tree-node>
</mat-tree>
`,
providers: [DynamicDatabase]
})
export class DynamicTreeExample {
treeControl: FlatTreeControl<DynamicFlatNode>;
dataSource: DynamicDataSource;
constructor(database: DynamicDatabase) {
this.treeControl = new FlatTreeControl<DynamicFlatNode>(
this.getLevel,
this.isExpandable
);
this.dataSource = new DynamicDataSource(this.treeControl, database);
database.initialize();
}
getLevel = (node: DynamicFlatNode) => node.level;
isExpandable = (node: DynamicFlatNode) => node.expandable;
hasChild = (_: number, nodeData: DynamicFlatNode) => nodeData.expandable;
loadChildren(node: DynamicFlatNode) {
if (this.treeControl.isExpanded(node)) {
// Load children dynamically
}
}
}
Accessibility
Keyboard Navigation
UP_ARROW: Move focus to previous node
DOWN_ARROW: Move focus to next node
LEFT_ARROW: Collapse node or move to parent
RIGHT_ARROW: Expand node or move to first child
HOME: Move to first node
END: Move to last node
ENTER or SPACE: Activate/select node
The tree has role="tree" and nodes have role="treeitem". Use appropriate labels:
<mat-tree aria-label="File system navigator">
<mat-tree-node>...</mat-tree-node>
</mat-tree>
Screen Readers
Provide clear labels for toggle buttons:
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'Toggle ' + node.name">
<mat-icon>chevron_right</mat-icon>
</button>
Styling
// Tree node indentation
mat-tree-node {
min-height: 48px;
padding-left: 24px;
}
// Nested node indentation
.mat-tree-node {
&[aria-level="1"] { padding-left: 24px; }
&[aria-level="2"] { padding-left: 48px; }
&[aria-level="3"] { padding-left: 72px; }
}
// Hide nested children when collapsed
.tree-invisible {
display: none;
}
// Node hover state
mat-tree-node:hover {
background-color: rgba(0, 0, 0, 0.04);
}
// Selected node
mat-tree-node.selected {
background-color: rgba(63, 81, 181, 0.1);
}
Data Sources
Array Data Source
const data = [/* tree data */];
this.dataSource = data;
Observable Data Source
const data$ = of([/* tree data */]);
this.dataSource = data$;
MatTreeFlatDataSource
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
const treeFlattener = new MatTreeFlattener(
transformer,
getLevel,
isExpandable,
getChildren
);
this.dataSource = new MatTreeFlatDataSource(this.treeControl, treeFlattener);
this.dataSource.data = TREE_DATA;
Best Practices
- Clear hierarchy: Use consistent indentation
- Visual feedback: Show expand/collapse state clearly
- Performance: Use flat tree for large datasets
- Loading states: Show progress for dynamic loading
- Keyboard support: Ensure full keyboard navigation
- ARIA labels: Provide descriptive labels
Theming
@use '@angular/material' as mat;
$theme: mat.define-theme((
color: (
theme-type: light,
primary: mat.$violet-palette,
),
));
html {
@include mat.tree-theme($theme);
}
TreeControl
interface TreeControl<T> {
dataNodes: T[];
expansionModel: SelectionModel<T>;
getLevel: (dataNode: T) => number;
isExpandable: (dataNode: T) => boolean;
getChildren: (dataNode: T) => Observable<T[]> | T[] | undefined | null;
}