diff --git a/src/components-examples/material/tree/index.ts b/src/components-examples/material/tree/index.ts
index efc466c63794..021f185c635b 100644
--- a/src/components-examples/material/tree/index.ts
+++ b/src/components-examples/material/tree/index.ts
@@ -1,6 +1,3 @@
-export {TreeDynamicExample} from './tree-dynamic/tree-dynamic-example';
export {TreeFlatOverviewExample} from './tree-flat-overview/tree-flat-overview-example';
export {TreeHarnessExample} from './tree-harness/tree-harness-example';
-export {TreeLoadmoreExample} from './tree-loadmore/tree-loadmore-example';
export {TreeNestedOverviewExample} from './tree-nested-overview/tree-nested-overview-example';
-export {TreeLegacyKeyboardInterfaceExample} from './tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example';
diff --git a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.css b/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.css
deleted file mode 100644
index c74ab7bd3141..000000000000
--- a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.example-tree-progress-bar {
- margin-left: 30px;
-}
diff --git a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html b/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html
deleted file mode 100644
index fdce4887c32a..000000000000
--- a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- {{node.item}}
-
-
-
- {{node.item}}
- @if (node.isLoading()) {
-
- }
-
-
diff --git a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.ts b/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.ts
deleted file mode 100644
index f45535688959..000000000000
--- a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-import {CollectionViewer, SelectionChange, DataSource} from '@angular/cdk/collections';
-import {FlatTreeControl} from '@angular/cdk/tree';
-import {ChangeDetectionStrategy, Component, Injectable, inject, signal} from '@angular/core';
-import {BehaviorSubject, merge, Observable} from 'rxjs';
-import {map} from 'rxjs/operators';
-import {MatProgressBarModule} from '@angular/material/progress-bar';
-import {MatIconModule} from '@angular/material/icon';
-import {MatButtonModule} from '@angular/material/button';
-import {MatTreeModule} from '@angular/material/tree';
-
-/** Flat node with expandable and level information */
-export class DynamicFlatNode {
- constructor(
- public item: string,
- public level = 1,
- public expandable = false,
- public isLoading = signal(false),
- ) {}
-}
-
-/**
- * Database for dynamic data. When expanding a node in the tree, the data source will need to fetch
- * the descendants data from the database.
- */
-@Injectable({providedIn: 'root'})
-export class DynamicDatabase {
- dataMap = new Map([
- ['Fruits', ['Apple', 'Orange', 'Banana']],
- ['Vegetables', ['Tomato', 'Potato', 'Onion']],
- ['Apple', ['Fuji', 'Macintosh']],
- ['Onion', ['Yellow', 'White', 'Purple']],
- ]);
-
- rootLevelNodes: string[] = ['Fruits', 'Vegetables'];
-
- /** Initial data from database */
- initialData(): DynamicFlatNode[] {
- return this.rootLevelNodes.map(name => new DynamicFlatNode(name, 0, true));
- }
-
- getChildren(node: string): string[] | undefined {
- return this.dataMap.get(node);
- }
-
- isExpandable(node: string): boolean {
- return this.dataMap.has(node);
- }
-}
-/**
- * File database, it can build a tree structured Json object from string.
- * Each node in Json object represents a file or a directory. For a file, it has filename and type.
- * For a directory, it has filename and children (a list of files or directories).
- * The input will be a json object string, and the output is a list of `FileNode` with nested
- * structure.
- */
-export class DynamicDataSource implements DataSource {
- dataChange = new BehaviorSubject([]);
-
- get data(): DynamicFlatNode[] {
- return this.dataChange.value;
- }
- set data(value: DynamicFlatNode[]) {
- this._treeControl.dataNodes = value;
- this.dataChange.next(value);
- }
-
- constructor(
- private _treeControl: FlatTreeControl,
- private _database: DynamicDatabase,
- ) {}
-
- connect(collectionViewer: CollectionViewer): Observable {
- this._treeControl.expansionModel.changed.subscribe(change => {
- if (
- (change as SelectionChange).added ||
- (change as SelectionChange).removed
- ) {
- this.handleTreeControl(change as SelectionChange);
- }
- });
-
- return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
- }
-
- disconnect(collectionViewer: CollectionViewer): void {}
-
- /** Handle expand/collapse behaviors */
- handleTreeControl(change: SelectionChange) {
- if (change.added) {
- change.added.forEach(node => this.toggleNode(node, true));
- }
- if (change.removed) {
- change.removed
- .slice()
- .reverse()
- .forEach(node => this.toggleNode(node, false));
- }
- }
-
- /**
- * Toggle the node, remove from display list
- */
- toggleNode(node: DynamicFlatNode, expand: boolean) {
- const children = this._database.getChildren(node.item);
- const index = this.data.indexOf(node);
- if (!children || index < 0) {
- // If no children, or cannot find the node, no op
- return;
- }
-
- node.isLoading.set(true);
-
- setTimeout(() => {
- if (expand) {
- const nodes = children.map(
- name => new DynamicFlatNode(name, node.level + 1, this._database.isExpandable(name)),
- );
- this.data.splice(index + 1, 0, ...nodes);
- } else {
- let count = 0;
- for (
- let i = index + 1;
- i < this.data.length && this.data[i].level > node.level;
- i++, count++
- ) {}
- this.data.splice(index + 1, count);
- }
-
- // notify the change
- this.dataChange.next(this.data);
- node.isLoading.set(false);
- }, 1000);
- }
-}
-
-/**
- * @title Tree with dynamic data
- */
-@Component({
- selector: 'tree-dynamic-example',
- templateUrl: 'tree-dynamic-example.html',
- styleUrl: 'tree-dynamic-example.css',
- standalone: true,
- imports: [MatTreeModule, MatButtonModule, MatIconModule, MatProgressBarModule],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class TreeDynamicExample {
- constructor() {
- const database = inject(DynamicDatabase);
-
- this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable);
- this.dataSource = new DynamicDataSource(this.treeControl, database);
-
- this.dataSource.data = database.initialData();
- }
-
- treeControl: FlatTreeControl;
-
- dataSource: DynamicDataSource;
-
- getLevel = (node: DynamicFlatNode) => node.level;
-
- isExpandable = (node: DynamicFlatNode) => node.expandable;
-
- hasChild = (_: number, _nodeData: DynamicFlatNode) => _nodeData.expandable;
-}
diff --git a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html
index c27428b5d280..93960b8d0f7d 100644
--- a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html
+++ b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html
@@ -1,17 +1,22 @@
-
+
-
+
{{node.name}}
-
{{node.name}}
diff --git a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts
index 2ea36a1671ed..43fa8c0ec95b 100644
--- a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts
+++ b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts
@@ -1,45 +1,34 @@
-import {FlatTreeControl} from '@angular/cdk/tree';
-import {ChangeDetectionStrategy, Component} from '@angular/core';
-import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree';
+import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core';
+import {MatTree, MatTreeModule} from '@angular/material/tree';
import {MatIconModule} from '@angular/material/icon';
import {MatButtonModule} from '@angular/material/button';
+import {ArrayDataSource} from '@angular/cdk/collections';
/**
- * Food data with nested structure.
- * Each node has a name and an optional list of children.
+ * Food data with a flat structure.
+ * Each node has a name and the level it should display. The order
+ * is used such that an item is a child of the previous item if the
+ * level is increased.
*/
interface FoodNode {
name: string;
- children?: FoodNode[];
+ level: number;
}
const TREE_DATA: FoodNode[] = [
- {
- name: 'Fruit',
- children: [{name: 'Apple'}, {name: 'Banana'}, {name: 'Fruit loops'}],
- },
- {
- name: 'Vegetables',
- children: [
- {
- name: 'Green',
- children: [{name: 'Broccoli'}, {name: 'Brussels sprouts'}],
- },
- {
- name: 'Orange',
- children: [{name: 'Pumpkins'}, {name: 'Carrots'}],
- },
- ],
- },
+ {name: 'Fruit', level: 0},
+ {name: 'Apple', level: 1},
+ {name: 'Banana', level: 1},
+ {name: 'Fruit loops', level: 1},
+ {name: 'Vegetables', level: 0},
+ {name: 'Green', level: 1},
+ {name: 'Broccoli', level: 2},
+ {name: 'Brussels spouts', level: 2},
+ {name: 'Orange', level: 1},
+ {name: 'Pumpkins', level: 2},
+ {name: 'Carrots', level: 2},
];
-/** Flat node with expandable and level information */
-interface ExampleFlatNode {
- expandable: boolean;
- name: string;
- level: number;
-}
-
/**
* @title Tree with flat nodes
*/
@@ -51,31 +40,33 @@ interface ExampleFlatNode {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TreeFlatOverviewExample {
- private _transformer = (node: FoodNode, level: number) => {
- return {
- expandable: !!node.children && node.children.length > 0,
- name: node.name,
- level: level,
- };
- };
+ @ViewChild(MatTree) tree: MatTree;
- treeControl = new FlatTreeControl(
- node => node.level,
- node => node.expandable,
- );
+ dataSource = new ArrayDataSource(TREE_DATA);
- treeFlattener = new MatTreeFlattener(
- this._transformer,
- node => node.level,
- node => node.expandable,
- node => node.children,
- );
+ levelAccessor = (dataNode: FoodNode) => dataNode.level;
- dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
+ hasChild = (index: number, node: FoodNode) => {
+ return node.level < TREE_DATA[index + 1]?.level;
+ };
- constructor() {
- this.dataSource.data = TREE_DATA;
+ shouldRender(node: FoodNode): boolean {
+ // This node should render if it is a root node or if all of its ancestors are expanded.
+ const parent = this._getParentNode(node);
+ return !parent || (!!this.tree?.isExpanded(parent) && this.shouldRender(parent));
}
- hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
+ private _getParentNode(node: FoodNode) {
+ const nodeIndex = TREE_DATA.indexOf(node);
+
+ // Determine the node's parent by finding the first preceding node that's
+ // one level shallower.
+ for (let i = nodeIndex - 1; i >= 0; i--) {
+ if (TREE_DATA[i].level === node.level - 1) {
+ return TREE_DATA[i];
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/components-examples/material/tree/tree-harness/tree-harness-example.html b/src/components-examples/material/tree/tree-harness/tree-harness-example.html
index c27428b5d280..f0a615e24861 100644
--- a/src/components-examples/material/tree/tree-harness/tree-harness-example.html
+++ b/src/components-examples/material/tree/tree-harness/tree-harness-example.html
@@ -1,4 +1,4 @@
-
+
@@ -11,7 +11,7 @@
{{node.name}}
diff --git a/src/components-examples/material/tree/tree-harness/tree-harness-example.spec.ts b/src/components-examples/material/tree/tree-harness/tree-harness-example.spec.ts
index 2c9f5f38241c..80f96f64898b 100644
--- a/src/components-examples/material/tree/tree-harness/tree-harness-example.spec.ts
+++ b/src/components-examples/material/tree/tree-harness/tree-harness-example.spec.ts
@@ -18,7 +18,7 @@ describe('TreeHarnessExample', () => {
const tree = await loader.getHarness(MatTreeHarness);
const treeDescendants = await tree.getNodes();
- // flat nodes are not rendered until expanded
+ // Nodes are not rendered until expanded
expect(treeDescendants.length).toBe(2);
await treeDescendants[0].expand();
@@ -28,11 +28,11 @@ describe('TreeHarnessExample', () => {
it('should correctly get correct node with text', async () => {
const tree = await loader.getHarness(MatTreeHarness);
- const treeNodes = await tree.getNodes({text: /Flat Group/});
+ const treeNodes = await tree.getNodes({text: /Example Group/});
expect(treeNodes.length).toBe(2);
const secondGroup = treeNodes[0];
- expect(await secondGroup.getText()).toBe('Flat Group 1');
+ expect(await secondGroup.getText()).toBe('Example Group 1');
expect(await secondGroup.getLevel()).toBe(1);
expect(await secondGroup.isDisabled()).toBe(false);
expect(await secondGroup.isExpanded()).toBe(false);
@@ -42,19 +42,23 @@ describe('TreeHarnessExample', () => {
const tree = await loader.getHarness(MatTreeHarness);
expect(await tree.getTreeStructure()).toEqual({
- children: [{text: 'Flat Group 1'}, {text: 'Flat Group 2'}],
+ children: [{text: 'Example Group 1'}, {text: 'Example Group 2'}],
});
- const firstGroup = (await tree.getNodes({text: /Flat Group 1/}))[0];
+ const firstGroup = (await tree.getNodes({text: /Example Group 1/}))[0];
await firstGroup.expand();
expect(await tree.getTreeStructure()).toEqual({
children: [
{
- text: 'Flat Group 1',
- children: [{text: 'Flat Leaf 1.1'}, {text: 'Flat Leaf 1.2'}, {text: 'Flat Leaf 1.3'}],
+ text: 'Example Group 1',
+ children: [
+ {text: 'Example Leaf 1.1'},
+ {text: 'Example Leaf 1.2'},
+ {text: 'Example Leaf 1.3'},
+ ],
},
- {text: 'Flat Group 2'},
+ {text: 'Example Group 2'},
],
});
});
diff --git a/src/components-examples/material/tree/tree-harness/tree-harness-example.ts b/src/components-examples/material/tree/tree-harness/tree-harness-example.ts
index e67f00ac9a15..7484b72f3c44 100644
--- a/src/components-examples/material/tree/tree-harness/tree-harness-example.ts
+++ b/src/components-examples/material/tree/tree-harness/tree-harness-example.ts
@@ -1,6 +1,5 @@
-import {FlatTreeControl} from '@angular/cdk/tree';
import {ChangeDetectionStrategy, Component} from '@angular/core';
-import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree';
+import {MatTreeModule, MatTreeNestedDataSource} from '@angular/material/tree';
import {MatIconModule} from '@angular/material/icon';
import {MatButtonModule} from '@angular/material/button';
@@ -9,28 +8,26 @@ interface Node {
children?: Node[];
}
-const FLAT_TREE_DATA: Node[] = [
+const TREE_DATA: Node[] = [
{
- name: 'Flat Group 1',
- children: [{name: 'Flat Leaf 1.1'}, {name: 'Flat Leaf 1.2'}, {name: 'Flat Leaf 1.3'}],
+ name: 'Example Group 1',
+ children: [{name: 'Example Leaf 1.1'}, {name: 'Example Leaf 1.2'}, {name: 'Example Leaf 1.3'}],
},
{
- name: 'Flat Group 2',
+ name: 'Example Group 2',
children: [
{
- name: 'Flat Group 2.1',
- children: [{name: 'Flat Leaf 2.1.1'}, {name: 'Flat Leaf 2.1.2'}, {name: 'Flat Leaf 2.1.3'}],
+ name: 'Example Group 2.1',
+ children: [
+ {name: 'Example Leaf 2.1.1'},
+ {name: 'Example Leaf 2.1.2'},
+ {name: 'Example Leaf 2.1.3'},
+ ],
},
],
},
];
-interface ExampleFlatNode {
- expandable: boolean;
- name: string;
- level: number;
-}
-
/**
* @title Testing with MatTreeHarness
*/
@@ -42,31 +39,13 @@ interface ExampleFlatNode {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TreeHarnessExample {
- private _transformer = (node: Node, level: number) => {
- return {
- expandable: !!node.children && node.children.length > 0,
- name: node.name,
- level: level,
- };
- };
-
- treeControl = new FlatTreeControl(
- 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);
+ dataSource = new MatTreeNestedDataSource();
constructor() {
- this.dataSource.data = FLAT_TREE_DATA;
+ this.dataSource.data = TREE_DATA;
}
- hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
+ childrenAccessor = (node: Node) => node.children || [];
+
+ hasChild = (_: number, node: Node) => !!node.children && node.children.length > 0;
}
diff --git a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.css b/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.css
deleted file mode 100644
index a88255f0d954..000000000000
--- a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.example-tree-node {
- display: flex;
- align-items: center;
-}
diff --git a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.html b/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.html
deleted file mode 100644
index dc133a66ca8e..000000000000
--- a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
- {{node.name}}
-
-
-
- {{node.name}}
-
-
diff --git a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.ts b/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.ts
deleted file mode 100644
index bbe854a191ea..000000000000
--- a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import {ChangeDetectionStrategy, Component} from '@angular/core';
-import {ArrayDataSource} from '@angular/cdk/collections';
-import {FlatTreeControl} from '@angular/cdk/tree';
-import {MatIconModule} from '@angular/material/icon';
-import {MatButtonModule} from '@angular/material/button';
-import {NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER} from '@angular/cdk/a11y';
-import {MatTreeModule} from '@angular/material/tree';
-
-const TREE_DATA: ExampleFlatNode[] = [
- {
- name: 'Fruit',
- expandable: true,
- level: 0,
- },
- {
- name: 'Apple',
- expandable: false,
- level: 1,
- },
- {
- name: 'Banana',
- expandable: false,
- level: 1,
- },
- {
- name: 'Fruit loops',
- expandable: false,
- level: 1,
- },
- {
- name: 'Vegetables',
- expandable: true,
- level: 0,
- },
- {
- name: 'Green',
- expandable: true,
- level: 1,
- },
- {
- name: 'Broccoli',
- expandable: false,
- level: 2,
- },
- {
- name: 'Brussels sprouts',
- expandable: false,
- level: 2,
- },
- {
- name: 'Orange',
- expandable: true,
- level: 1,
- },
- {
- name: 'Pumpkins',
- expandable: false,
- level: 2,
- },
- {
- name: 'Carrots',
- expandable: false,
- level: 2,
- },
-];
-
-/** Flat node with expandable and level information */
-interface ExampleFlatNode {
- expandable: boolean;
- name: string;
- level: number;
-}
-
-/**
- * @title Tree with flat nodes
- */
-@Component({
- selector: 'tree-legacy-keyboard-interface-example',
- templateUrl: 'tree-legacy-keyboard-interface-example.html',
- styleUrls: ['tree-legacy-keyboard-interface-example.css'],
- standalone: true,
- imports: [MatTreeModule, MatButtonModule, MatIconModule],
- providers: [NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class TreeLegacyKeyboardInterfaceExample {
- treeControl = new FlatTreeControl(
- node => node.level,
- node => node.expandable,
- );
-
- dataSource = new ArrayDataSource(TREE_DATA);
-
- hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
-
- getParentNode(node: ExampleFlatNode) {
- const nodeIndex = TREE_DATA.indexOf(node);
-
- for (let i = nodeIndex - 1; i >= 0; i--) {
- if (TREE_DATA[i].level === node.level - 1) {
- return TREE_DATA[i];
- }
- }
-
- return null;
- }
-}
diff --git a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.css b/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.css
deleted file mode 100644
index db0ecf99e2de..000000000000
--- a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.example-load-more {
- border-radius: 10px;
- padding-left: 15px;
- padding-right: 15px;
- cursor: pointer;
-}
-.example-load-more:focus {
- /*
- Display a focus state for the "Load More" button.
- 0.12 is a common value in Material Design
- */
- background: rgba(0, 0, 0, 0.12);
-}
-.example-load-more:hover {
- /*
- Display a focus state for the "Load More" button.
- 0.04 is a common value in Material Design
- */
- background: rgba(0, 0, 0, 0.04);
-}
diff --git a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html b/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html
deleted file mode 100644
index 28ed67278f8f..000000000000
--- a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
- {{node.name}}
-
-
-
-
-
- {{node.name}}
-
-
-
- Load more of {{node.parent}}...
-
-
diff --git a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.ts b/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.ts
deleted file mode 100644
index 4aba573cd8ef..000000000000
--- a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.ts
+++ /dev/null
@@ -1,220 +0,0 @@
-/**
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-import {FlatTreeControl} from '@angular/cdk/tree';
-import {ChangeDetectionStrategy, Component, Injectable, inject} from '@angular/core';
-import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree';
-import {BehaviorSubject, Observable} from 'rxjs';
-import {MatIconModule} from '@angular/material/icon';
-import {MatButtonModule} from '@angular/material/button';
-import {ENTER, SPACE} from '@angular/cdk/keycodes';
-
-const LOAD_MORE = 'LOAD_MORE';
-let loadMoreId = 1;
-
-/** Nested node */
-export class NestedNode {
- childrenChange = new BehaviorSubject([]);
-
- get children(): NestedNode[] {
- return this.childrenChange.value;
- }
-
- constructor(
- public name: string,
- public hasChildren = false,
- public parent: string | null = null,
- public isLoadMore = false,
- ) {}
-}
-
-/** Flat node with expandable and level information */
-export class FlatNode {
- constructor(
- public name: string,
- public level = 1,
- public expandable = false,
- public parent: string | null = null,
- public isLoadMore = false,
- ) {}
-}
-
-/** Number of nodes loaded at a time */
-const batchSize = 3;
-
-/**
- * A database that only load part of the data initially. After user clicks on the `Load more`
- * button, more data will be loaded.
- */
-@Injectable()
-export class LoadmoreDatabase {
- /** Map of node name to node */
- nodes = new Map();
-
- dataChange = new BehaviorSubject([]);
-
- /** Example data */
- rootNodes: string[] = ['Vegetables', 'Fruits'];
- childMap = new Map([
- ['Fruits', ['Apple', 'Orange', 'Banana']],
- ['Vegetables', ['Tomato', 'Potato', 'Onion']],
- [
- 'Apple',
- [
- 'Gala',
- 'Braeburn',
- 'Fuji',
- 'Macintosh',
- 'Golden Delicious',
- 'Red Delicious',
- 'Empire',
- 'Granny Smith',
- 'Cameo',
- 'Baldwin',
- 'Jonagold',
- ],
- ],
- ['Onion', ['Yellow', 'White', 'Purple', 'Green', 'Shallot', 'Sweet', 'Red', 'Leek']],
- ]);
-
- initialize() {
- const data = this.rootNodes.map(name => this._generateNode(name, null));
- this.dataChange.next(data);
- }
-
- /** Expand a node whose children are not loaded */
- loadChildren(name: string, onlyFirstTime = false) {
- if (!this.nodes.has(name) || !this.childMap.has(name)) {
- return;
- }
- const parent = this.nodes.get(name)!;
- const children = this.childMap.get(name)!;
-
- if (onlyFirstTime && parent.children!.length > 0) {
- return;
- }
-
- const newChildrenNumber = parent.children!.length + batchSize;
- const nodes = children
- .slice(0, newChildrenNumber)
- .map(name => this._generateNode(name, parent.name));
- if (newChildrenNumber < children.length) {
- // Need a new "Load More" node
- nodes.push(new NestedNode(`${LOAD_MORE}-${loadMoreId++}`, false, name, true));
- }
-
- parent.childrenChange.next(nodes);
- this.dataChange.next(this.dataChange.value);
- }
-
- private _generateNode(name: string, parent: string | null): NestedNode {
- if (!this.nodes.has(name)) {
- this.nodes.set(name, new NestedNode(name, this.childMap.has(name), parent));
- }
-
- return this.nodes.get(name)!;
- }
-}
-
-/**
- * @title Tree with partially loaded data
- */
-@Component({
- selector: 'tree-loadmore-example',
- templateUrl: 'tree-loadmore-example.html',
- styleUrl: 'tree-loadmore-example.css',
- providers: [LoadmoreDatabase],
- standalone: true,
- imports: [MatTreeModule, MatButtonModule, MatIconModule],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class TreeLoadmoreExample {
- private _database = inject(LoadmoreDatabase);
-
- nodeMap = new Map();
- treeControl: FlatTreeControl;
- treeFlattener: MatTreeFlattener;
- // Flat tree data source
- dataSource: MatTreeFlatDataSource;
-
- constructor() {
- const _database = this._database;
-
- this.treeFlattener = new MatTreeFlattener(
- this.transformer,
- this.getLevel,
- this.isExpandable,
- this.getChildren,
- );
-
- // TODO(#27626): Remove treeControl. Adopt either levelAccessor or childrenAccessor.
- this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable);
-
- this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
-
- _database.dataChange.subscribe(data => {
- this.dataSource.data = data;
- });
-
- _database.initialize();
- }
-
- getChildren = (node: NestedNode): Observable => node.childrenChange;
-
- transformer = (node: NestedNode, level: number) => {
- const existingNode = this.nodeMap.get(node.name);
-
- if (existingNode) {
- return existingNode;
- }
-
- const newNode = new FlatNode(node.name, level, node.hasChildren, node.parent, node.isLoadMore);
- this.nodeMap.set(node.name, newNode);
- return newNode;
- };
-
- getLevel = (node: FlatNode) => node.level;
-
- isExpandable = (node: FlatNode) => node.expandable;
-
- hasChild = (_: number, node: FlatNode) => node.expandable;
-
- isLoadMore = (_: number, node: FlatNode) => node.isLoadMore;
-
- loadChildren(node: FlatNode) {
- this._database.loadChildren(node.name, true);
- }
-
- /** Load more nodes when clicking on "Load more" node. */
- loadOnClick(event: MouseEvent, node: FlatNode) {
- this._loadSiblings(event.target as HTMLElement, node);
- }
-
- /** Load more nodes on keyboardpress when focused on "Load more" node */
- loadOnKeypress(event: KeyboardEvent, node: FlatNode) {
- if (event.keyCode === ENTER || event.keyCode === SPACE) {
- this._loadSiblings(event.target as HTMLElement, node);
- }
- }
-
- private _loadSiblings(nodeElement: HTMLElement, node: FlatNode) {
- if (node.parent) {
- // Store a reference to the sibling of the "Load More" node before it is removed from the DOM
- const previousSibling = nodeElement.previousElementSibling;
-
- // Synchronously load data.
- this._database.loadChildren(node.parent);
-
- const focusDesination = previousSibling?.nextElementSibling || previousSibling;
-
- if (focusDesination) {
- // Restore focus.
- (focusDesination as HTMLElement).focus();
- }
- }
- }
-}
diff --git a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html
index c827115b1680..c18934525142 100644
--- a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html
+++ b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html
@@ -1,4 +1,4 @@
-
+
@@ -13,14 +13,14 @@
{{node.name}}
-
diff --git a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts
index b9387e9f8d5b..20f707d5b1b4 100644
--- a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts
+++ b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts
@@ -1,4 +1,3 @@
-import {NestedTreeControl} from '@angular/cdk/tree';
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {MatTreeNestedDataSource, MatTreeModule} from '@angular/material/tree';
import {MatIconModule} from '@angular/material/icon';
@@ -45,12 +44,13 @@ const TREE_DATA: FoodNode[] = [
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TreeNestedOverviewExample {
- treeControl = new NestedTreeControl(node => node.children);
dataSource = new MatTreeNestedDataSource();
constructor() {
this.dataSource.data = TREE_DATA;
}
+ childrenAccessor = (node: FoodNode) => node.children || [];
+
hasChild = (_: number, node: FoodNode) => !!node.children && node.children.length > 0;
}
diff --git a/src/dev-app/tree/tree-demo.html b/src/dev-app/tree/tree-demo.html
index 52d4216c2aee..af214c89be6e 100644
--- a/src/dev-app/tree/tree-demo.html
+++ b/src/dev-app/tree/tree-demo.html
@@ -31,14 +31,6 @@
CDK Nested tree (childrenAccessor)
-
- Dynamic flat tree
-
-
-
- Load more flat tree
-
-
Complex tree (Redux pattern)
@@ -47,8 +39,4 @@
Custom Key Manager
-
- Legacy Keyboard Interface
-
-
diff --git a/src/dev-app/tree/tree-demo.ts b/src/dev-app/tree/tree-demo.ts
index 0a2105780316..cb14f9fdddfb 100644
--- a/src/dev-app/tree/tree-demo.ts
+++ b/src/dev-app/tree/tree-demo.ts
@@ -18,10 +18,7 @@ import {
CdkTreeCustomKeyManagerExample,
} from '@angular/components-examples/cdk/tree';
import {
- TreeDynamicExample,
TreeFlatOverviewExample,
- TreeLegacyKeyboardInterfaceExample,
- TreeLoadmoreExample,
TreeNestedOverviewExample,
} from '@angular/components-examples/material/tree';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@@ -52,10 +49,7 @@ import {MatTreeModule} from '@angular/material/tree';
CdkTreeComplexExample,
CommonModule,
FormsModule,
- TreeDynamicExample,
TreeFlatOverviewExample,
- TreeLegacyKeyboardInterfaceExample,
- TreeLoadmoreExample,
TreeNestedOverviewExample,
MatButtonModule,
MatExpansionModule,