Skip to content

Commit a91f283

Browse files
olagokemillscrisbeto
authored andcommitted
docs(material/tree): update examples to use modern childrenAccessor API (angular#32751)
Removed deprecated FlatTreeControl, NestedTreeControl, MatTreeFlattener, and MatTreeFlatDataSource from all mat-tree examples. Updated to use the modern childrenAccessor pattern. Fixes angular#31524 (cherry picked from commit 9055ac5)
1 parent abf30cb commit a91f283

File tree

10 files changed

+153
-338
lines changed

10 files changed

+153
-338
lines changed
Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
1+
<mat-tree #tree [dataSource]="dataSource" [childrenAccessor]="childrenAccessor">
22
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
33
<button matIconButton disabled></button>
4-
{{node.item}}
4+
{{node.name}}
55
</mat-tree-node>
66
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding matTreeNodeToggle
7-
[cdkTreeNodeTypeaheadLabel]="node.item">
8-
<button matIconButton
9-
[attr.aria-label]="'Toggle ' + node.item" matTreeNodeToggle>
7+
[cdkTreeNodeTypeaheadLabel]="node.name" (expandedChange)="onNodeExpanded(node, $event)">
8+
<button matIconButton [attr.aria-label]="'Toggle ' + node.name" matTreeNodeToggle>
109
<mat-icon class="mat-icon-rtl-mirror">
11-
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
10+
{{tree.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
1211
</mat-icon>
1312
</button>
14-
{{node.item}}
13+
{{node.name}}
1514
@if (node.isLoading()) {
16-
<mat-progress-bar
17-
mode="indeterminate"
18-
class="example-tree-progress-bar"></mat-progress-bar>
15+
<mat-progress-bar mode="indeterminate" class="example-tree-progress-bar"></mat-progress-bar>
1916
}
2017
</mat-tree-node>
21-
</mat-tree>
18+
</mat-tree>
Lines changed: 47 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
1-
import {CollectionViewer, SelectionChange, DataSource} from '@angular/cdk/collections';
2-
import {FlatTreeControl} from '@angular/cdk/tree';
31
import {ChangeDetectionStrategy, Component, Injectable, inject, signal} from '@angular/core';
4-
import {BehaviorSubject, merge, Observable} from 'rxjs';
5-
import {map} from 'rxjs/operators';
62
import {MatProgressBarModule} from '@angular/material/progress-bar';
73
import {MatIconModule} from '@angular/material/icon';
84
import {MatButtonModule} from '@angular/material/button';
95
import {MatTreeModule} from '@angular/material/tree';
106

11-
/** Flat node with expandable and level information */
12-
class DynamicFlatNode {
13-
constructor(
14-
public item: string,
15-
public level = 1,
16-
public expandable = false,
17-
public isLoading = signal(false),
18-
) {}
7+
/** Node with expandable and level information */
8+
interface DynamicNode {
9+
name: string;
10+
level: number;
11+
expandable: boolean;
12+
isLoading: ReturnType<typeof signal<boolean>>;
13+
children?: DynamicNode[];
1914
}
2015

2116
/**
@@ -34,102 +29,26 @@ export class DynamicDatabase {
3429
rootLevelNodes: string[] = ['Fruits', 'Vegetables'];
3530

3631
/** Initial data from database */
37-
initialData(): DynamicFlatNode[] {
38-
return this.rootLevelNodes.map(name => new DynamicFlatNode(name, 0, true));
32+
initialData(): DynamicNode[] {
33+
return this.rootLevelNodes.map(name => this.createNode(name, 0, true));
3934
}
4035

41-
getChildren(node: string): string[] | undefined {
42-
return this.dataMap.get(node);
36+
createNode(name: string, level: number, expandable: boolean): DynamicNode {
37+
return {
38+
name,
39+
level,
40+
expandable,
41+
isLoading: signal(false),
42+
children: undefined,
43+
};
4344
}
4445

45-
isExpandable(node: string): boolean {
46-
return this.dataMap.has(node);
46+
getChildren(name: string): string[] | undefined {
47+
return this.dataMap.get(name);
4748
}
48-
}
49-
/**
50-
* File database, it can build a tree structured Json object from string.
51-
* Each node in Json object represents a file or a directory. For a file, it has filename and type.
52-
* For a directory, it has filename and children (a list of files or directories).
53-
* The input will be a json object string, and the output is a list of `FileNode` with nested
54-
* structure.
55-
*/
56-
export class DynamicDataSource implements DataSource<DynamicFlatNode> {
57-
dataChange = new BehaviorSubject<DynamicFlatNode[]>([]);
58-
59-
get data(): DynamicFlatNode[] {
60-
return this.dataChange.value;
61-
}
62-
set data(value: DynamicFlatNode[]) {
63-
this._treeControl.dataNodes = value;
64-
this.dataChange.next(value);
65-
}
66-
67-
constructor(
68-
private _treeControl: FlatTreeControl<DynamicFlatNode>,
69-
private _database: DynamicDatabase,
70-
) {}
71-
72-
connect(collectionViewer: CollectionViewer): Observable<DynamicFlatNode[]> {
73-
this._treeControl.expansionModel.changed.subscribe(change => {
74-
if (
75-
(change as SelectionChange<DynamicFlatNode>).added ||
76-
(change as SelectionChange<DynamicFlatNode>).removed
77-
) {
78-
this.handleTreeControl(change as SelectionChange<DynamicFlatNode>);
79-
}
80-
});
81-
82-
return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
83-
}
84-
85-
disconnect(collectionViewer: CollectionViewer): void {}
86-
87-
/** Handle expand/collapse behaviors */
88-
handleTreeControl(change: SelectionChange<DynamicFlatNode>) {
89-
if (change.added) {
90-
change.added.forEach(node => this.toggleNode(node, true));
91-
}
92-
if (change.removed) {
93-
change.removed
94-
.slice()
95-
.reverse()
96-
.forEach(node => this.toggleNode(node, false));
97-
}
98-
}
99-
100-
/**
101-
* Toggle the node, remove from display list
102-
*/
103-
toggleNode(node: DynamicFlatNode, expand: boolean) {
104-
const children = this._database.getChildren(node.item);
105-
const index = this.data.indexOf(node);
106-
if (!children || index < 0) {
107-
// If no children, or cannot find the node, no op
108-
return;
109-
}
110-
111-
node.isLoading.set(true);
112-
113-
setTimeout(() => {
114-
if (expand) {
115-
const nodes = children.map(
116-
name => new DynamicFlatNode(name, node.level + 1, this._database.isExpandable(name)),
117-
);
118-
this.data.splice(index + 1, 0, ...nodes);
119-
} else {
120-
let count = 0;
121-
for (
122-
let i = index + 1;
123-
i < this.data.length && this.data[i].level > node.level;
124-
i++, count++
125-
) {}
126-
this.data.splice(index + 1, count);
127-
}
12849

129-
// notify the change
130-
this.dataChange.next(this.data);
131-
node.isLoading.set(false);
132-
}, 1000);
50+
isExpandable(name: string): boolean {
51+
return this.dataMap.has(name);
13352
}
13453
}
13554

@@ -144,22 +63,37 @@ export class DynamicDataSource implements DataSource<DynamicFlatNode> {
14463
changeDetection: ChangeDetectionStrategy.OnPush,
14564
})
14665
export class TreeDynamicExample {
147-
constructor() {
148-
const database = inject(DynamicDatabase);
66+
private _database = inject(DynamicDatabase);
14967

150-
this.treeControl = new FlatTreeControl<DynamicFlatNode>(this.getLevel, this.isExpandable);
151-
this.dataSource = new DynamicDataSource(this.treeControl, database);
68+
dataSource = this._database.initialData();
15269

153-
this.dataSource.data = database.initialData();
154-
}
70+
childrenAccessor = (node: DynamicNode) => node.children ?? [];
15571

156-
treeControl: FlatTreeControl<DynamicFlatNode>;
72+
hasChild = (_: number, node: DynamicNode) => node.expandable;
15773

158-
dataSource: DynamicDataSource;
74+
/**
75+
* Load children on node expansion.
76+
* Called from template via (expandedChange) output.
77+
*/
78+
onNodeExpanded(node: DynamicNode, expanded: boolean): void {
79+
if (!expanded || node.children) {
80+
// Don't reload if collapsing or already loaded
81+
return;
82+
}
15983

160-
getLevel = (node: DynamicFlatNode) => node.level;
84+
const childNames = this._database.getChildren(node.name);
85+
if (!childNames) {
86+
return;
87+
}
16188

162-
isExpandable = (node: DynamicFlatNode) => node.expandable;
89+
node.isLoading.set(true);
16390

164-
hasChild = (_: number, _nodeData: DynamicFlatNode) => _nodeData.expandable;
91+
// Simulate async data loading
92+
setTimeout(() => {
93+
node.children = childNames.map(name =>
94+
this._database.createNode(name, node.level + 1, this._database.isExpandable(name)),
95+
);
96+
node.isLoading.set(false);
97+
}, 1000);
98+
}
16599
}

src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
1+
<mat-tree #tree [dataSource]="dataSource" [childrenAccessor]="childrenAccessor">
22
<!-- This is the tree node template for leaf nodes -->
33
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
44
<!-- use a disabled button to provide padding for tree leaf -->
@@ -11,7 +11,7 @@
1111
<button matIconButton matTreeNodeToggle
1212
[attr.aria-label]="'Toggle ' + node.name">
1313
<mat-icon class="mat-icon-rtl-mirror">
14-
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
14+
{{tree.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
1515
</mat-icon>
1616
</button>
1717
{{node.name}}

src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import {FlatTreeControl} from '@angular/cdk/tree';
21
import {ChangeDetectionStrategy, Component} from '@angular/core';
3-
import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree';
2+
import {MatTreeModule} from '@angular/material/tree';
43
import {MatIconModule} from '@angular/material/icon';
54
import {MatButtonModule} from '@angular/material/button';
65

@@ -13,13 +12,6 @@ interface FoodNode {
1312
children?: FoodNode[];
1413
}
1514

16-
/** Flat node with expandable and level information */
17-
interface ExampleFlatNode {
18-
expandable: boolean;
19-
name: string;
20-
level: number;
21-
}
22-
2315
/**
2416
* @title Tree with flat nodes
2517
*/
@@ -30,33 +22,11 @@ interface ExampleFlatNode {
3022
changeDetection: ChangeDetectionStrategy.OnPush,
3123
})
3224
export class TreeFlatOverviewExample {
33-
private _transformer = (node: FoodNode, level: number) => {
34-
return {
35-
expandable: !!node.children && node.children.length > 0,
36-
name: node.name,
37-
level: level,
38-
};
39-
};
40-
41-
treeControl = new FlatTreeControl<ExampleFlatNode>(
42-
node => node.level,
43-
node => node.expandable,
44-
);
45-
46-
treeFlattener = new MatTreeFlattener(
47-
this._transformer,
48-
node => node.level,
49-
node => node.expandable,
50-
node => node.children,
51-
);
52-
53-
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
25+
dataSource = EXAMPLE_DATA;
5426

55-
constructor() {
56-
this.dataSource.data = EXAMPLE_DATA;
57-
}
27+
childrenAccessor = (node: FoodNode) => node.children ?? [];
5828

59-
hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
29+
hasChild = (_: number, node: FoodNode) => !!node.children && node.children.length > 0;
6030
}
6131

6232
const EXAMPLE_DATA: FoodNode[] = [
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
1+
<mat-tree #tree [dataSource]="dataSource" [childrenAccessor]="childrenAccessor">
22
<!-- This is the tree node template for leaf nodes -->
33
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
44
<!-- use a disabled button to provide padding for tree leaf -->
@@ -7,13 +7,12 @@
77
</mat-tree-node>
88
<!-- This is the tree node template for expandable nodes -->
99
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding matTreeNodeToggle
10-
[cdkTreeNodeTypeaheadLabel]="node.name">
11-
<button matIconButton matTreeNodeToggle
12-
[attr.aria-label]="'Toggle ' + node.name">
10+
[cdkTreeNodeTypeaheadLabel]="node.name">
11+
<button matIconButton matTreeNodeToggle [attr.aria-label]="'Toggle ' + node.name">
1312
<mat-icon class="mat-icon-rtl-mirror">
14-
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
13+
{{tree.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
1514
</mat-icon>
1615
</button>
1716
{{node.name}}
1817
</mat-tree-node>
19-
</mat-tree>
18+
</mat-tree>

src/components-examples/material/tree/tree-harness/tree-harness-example.ts

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import {FlatTreeControl} from '@angular/cdk/tree';
21
import {ChangeDetectionStrategy, Component} from '@angular/core';
3-
import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree';
2+
import {MatTreeModule} from '@angular/material/tree';
43
import {MatIconModule} from '@angular/material/icon';
54
import {MatButtonModule} from '@angular/material/button';
65

@@ -9,12 +8,6 @@ interface Node {
98
children?: Node[];
109
}
1110

12-
interface ExampleFlatNode {
13-
expandable: boolean;
14-
name: string;
15-
level: number;
16-
}
17-
1811
/**
1912
* @title Testing with MatTreeHarness
2013
*/
@@ -25,33 +18,11 @@ interface ExampleFlatNode {
2518
changeDetection: ChangeDetectionStrategy.OnPush,
2619
})
2720
export class TreeHarnessExample {
28-
private _transformer = (node: Node, level: number) => {
29-
return {
30-
expandable: !!node.children && node.children.length > 0,
31-
name: node.name,
32-
level: level,
33-
};
34-
};
35-
36-
treeControl = new FlatTreeControl<ExampleFlatNode>(
37-
node => node.level,
38-
node => node.expandable,
39-
);
40-
41-
treeFlattener = new MatTreeFlattener(
42-
this._transformer,
43-
node => node.level,
44-
node => node.expandable,
45-
node => node.children,
46-
);
47-
48-
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
21+
dataSource = EXAMPLE_DATA;
4922

50-
constructor() {
51-
this.dataSource.data = EXAMPLE_DATA;
52-
}
23+
childrenAccessor = (node: Node) => node.children ?? [];
5324

54-
hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
25+
hasChild = (_: number, node: Node) => !!node.children && node.children.length > 0;
5526
}
5627

5728
const EXAMPLE_DATA: Node[] = [

0 commit comments

Comments
 (0)