Skip to content

Commit e2c7e1a

Browse files
authored
feat(cdk/tree): add trackBy to NestedTreeControl (#19602)
1 parent 51137b6 commit e2c7e1a

File tree

3 files changed

+45
-11
lines changed

3 files changed

+45
-11
lines changed

src/cdk/tree/control/nested-tree-control.spec.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import {NestedTreeControl} from './nested-tree-control';
44

55

66
describe('CdkNestedTreeControl', () => {
7-
let treeControl: NestedTreeControl<TestData>;
87
let getChildren = (node: TestData) => observableOf(node.children);
98

10-
beforeEach(() => {
11-
treeControl = new NestedTreeControl<TestData>(getChildren);
12-
});
13-
149
describe('base tree control actions', () => {
10+
let treeControl: NestedTreeControl<TestData>;
11+
12+
beforeEach(() => {
13+
treeControl = new NestedTreeControl<TestData>(getChildren);
14+
});
15+
1516
it('should be able to expand and collapse dataNodes', () => {
1617
const nodes = generateData(10, 4);
1718
const node = nodes[1];
@@ -196,6 +197,24 @@ describe('CdkNestedTreeControl', () => {
196197
});
197198
});
198199
});
200+
201+
it('maintains node expansion state based on trackBy function, if provided', () => {
202+
const treeControl = new NestedTreeControl<TestData, string>(
203+
getChildren, {trackBy: (node: TestData) => `${node.a} ${node.b} ${node.c}`});
204+
205+
const nodes = generateData(2, 2);
206+
const secondNode = nodes[1];
207+
treeControl.dataNodes = nodes;
208+
209+
treeControl.expand(secondNode);
210+
expect(treeControl.isExpanded(secondNode)).toBeTruthy('Expect second node to be expanded');
211+
212+
// Replace the second node with a brand new instance with same hash
213+
nodes[1] = new TestData(
214+
secondNode.a, secondNode.b, secondNode.c, secondNode.level, secondNode.children);
215+
expect(treeControl.isExpanded(nodes[1])).toBeTruthy('Expect second node to still be expanded');
216+
});
217+
199218
});
200219

201220
export class TestData {

src/cdk/tree/control/nested-tree-control.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,22 @@ import {Observable, isObservable} from 'rxjs';
99
import {take, filter} from 'rxjs/operators';
1010
import {BaseTreeControl} from './base-tree-control';
1111

12-
/** Nested tree control. Able to expand/collapse a subtree recursively for NestedNode type. */
13-
export class NestedTreeControl<T> extends BaseTreeControl<T> {
12+
/** Optional set of configuration that can be provided to the NestedTreeControl. */
13+
export interface NestedTreeControlOptions<T, K> {
14+
trackBy?: (dataNode: T) => K;
15+
}
1416

17+
/** Nested tree control. Able to expand/collapse a subtree recursively for NestedNode type. */
18+
export class NestedTreeControl<T, K = T> extends BaseTreeControl<T, K> {
1519
/** Construct with nested tree function getChildren. */
16-
constructor(public getChildren: (dataNode: T) => (Observable<T[]> | T[] | undefined | null)) {
20+
constructor(
21+
public getChildren: (dataNode: T) => (Observable<T[]>| T[] | undefined | null),
22+
public options?: NestedTreeControlOptions<T, K>) {
1723
super();
24+
25+
if (this.options) {
26+
this.trackBy = this.options.trackBy;
27+
}
1828
}
1929

2030
/**
@@ -27,7 +37,7 @@ export class NestedTreeControl<T> extends BaseTreeControl<T> {
2737
this.expansionModel.clear();
2838
const allNodes = this.dataNodes.reduce((accumulator: T[], dataNode) =>
2939
[...accumulator, ...this.getDescendants(dataNode), dataNode], []);
30-
this.expansionModel.select(...allNodes);
40+
this.expansionModel.select(...allNodes.map(node => this._trackByValue(node)));
3141
}
3242

3343
/** Gets a list of descendant dataNodes of a subtree rooted at given data node recursively. */

tools/public_api_guard/cdk/tree.d.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,19 @@ export declare function getTreeMultipleDefaultNodeDefsError(): Error;
161161

162162
export declare function getTreeNoValidDataSourceError(): Error;
163163

164-
export declare class NestedTreeControl<T> extends BaseTreeControl<T> {
164+
export declare class NestedTreeControl<T, K = T> extends BaseTreeControl<T, K> {
165165
getChildren: (dataNode: T) => (Observable<T[]> | T[] | undefined | null);
166-
constructor(getChildren: (dataNode: T) => (Observable<T[]> | T[] | undefined | null));
166+
options?: NestedTreeControlOptions<T, K> | undefined;
167+
constructor(getChildren: (dataNode: T) => (Observable<T[]> | T[] | undefined | null), options?: NestedTreeControlOptions<T, K> | undefined);
167168
protected _getDescendants(descendants: T[], dataNode: T): void;
168169
expandAll(): void;
169170
getDescendants(dataNode: T): T[];
170171
}
171172

173+
export interface NestedTreeControlOptions<T, K> {
174+
trackBy?: (dataNode: T) => K;
175+
}
176+
172177
export interface TreeControl<T, K = T> {
173178
dataNodes: T[];
174179
expansionModel: SelectionModel<K>;

0 commit comments

Comments
 (0)