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,