Skip to content

Commit d8a93e2

Browse files
committed
feat(cdk/tree): add isExpandable parameter to NestedTreeControl, fix some tests
1 parent a23f598 commit d8a93e2

File tree

6 files changed

+55
-13
lines changed

6 files changed

+55
-13
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {BaseTreeControl} from './base-tree-control';
1111

1212
/** Optional set of configuration that can be provided to the NestedTreeControl. */
1313
export interface NestedTreeControlOptions<T, K> {
14+
isExpandable?: (dataNode: T) => boolean;
1415
trackBy?: (dataNode: T) => K;
1516
}
1617

@@ -31,6 +32,10 @@ export class NestedTreeControl<T, K = T> extends BaseTreeControl<T, K> {
3132
if (this.options) {
3233
this.trackBy = this.options.trackBy;
3334
}
35+
36+
if (this.options?.isExpandable) {
37+
this.isExpandable = this.options.isExpandable;
38+
}
3439
}
3540

3641
/**

src/cdk/tree/nested-node.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import {
99
AfterContentInit,
10+
ChangeDetectorRef,
1011
ContentChildren,
1112
Directive,
1213
ElementRef,
@@ -60,9 +61,10 @@ export class CdkNestedTreeNode<T, K = T>
6061
constructor(
6162
elementRef: ElementRef<HTMLElement>,
6263
tree: CdkTree<T, K>,
64+
changeDetectorRef: ChangeDetectorRef,
6365
protected _differs: IterableDiffers,
6466
) {
65-
super(elementRef, tree);
67+
super(elementRef, tree, changeDetectorRef);
6668
}
6769

6870
ngAfterContentInit() {

src/cdk/tree/tree.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,7 +1488,6 @@ class NestedCdkTreeApp {
14881488
<cdk-nested-tree-node
14891489
*cdkTreeNodeDef="let node"
14901490
class="customNodeClass"
1491-
[isExpandable]="node.children.length > 0"
14921491
[isDisabled]="node.isDisabled">
14931492
{{node.pizzaTopping}} - {{node.pizzaCheese}} + {{node.pizzaBase}}
14941493
<ng-template cdkTreeNodeOutlet></ng-template>
@@ -1499,7 +1498,9 @@ class NestedCdkTreeApp {
14991498
class StaticNestedCdkTreeApp {
15001499
getChildren = (node: TestData) => node.children;
15011500

1502-
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);
1501+
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren, {
1502+
isExpandable: node => node.children.length > 0,
1503+
});
15031504

15041505
dataSource: FakeDataSource;
15051506

@@ -1569,7 +1570,6 @@ class CdkTreeAppWithToggle {
15691570
template: `
15701571
<cdk-tree [dataSource]="dataSource" [treeControl]="treeControl">
15711572
<cdk-nested-tree-node *cdkTreeNodeDef="let node" class="customNodeClass"
1572-
[isExpandable]="isExpandable(node) | async"
15731573
cdkTreeNodeToggle
15741574
[cdkTreeNodeToggleRecursive]="toggleRecursively">
15751575
{{node.pizzaTopping}} - {{node.pizzaCheese}} + {{node.pizzaBase}}
@@ -1584,10 +1584,10 @@ class NestedCdkTreeAppWithToggle {
15841584
toggleRecursively: boolean = true;
15851585

15861586
getChildren = (node: TestData) => node.observableChildren;
1587-
isExpandable = (node: TestData) =>
1588-
node.observableChildren.pipe(map(children => children.length > 0));
15891587

1590-
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);
1588+
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren, {
1589+
isExpandable: node => node.children.length > 0,
1590+
});
15911591
dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);
15921592

15931593
@ViewChild(CdkTree) tree: CdkTree<TestData>;

src/cdk/tree/tree.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
take,
5555
takeUntil,
5656
tap,
57+
withLatestFrom,
5758
} from 'rxjs/operators';
5859
import {TreeControl} from './control/tree-control';
5960
import {CdkTreeNodeDef, CdkTreeNodeOutletContext} from './node';
@@ -260,9 +261,26 @@ export class CdkTree<T, K = T>
260261
}
261262
}
262263

264+
let expansionModel;
263265
if (!this.treeControl) {
264-
this._expansionModel = new SelectionModel(true);
266+
expansionModel = new SelectionModel<K>(true);
267+
this._expansionModel = expansionModel;
268+
} else {
269+
expansionModel = this.treeControl.expansionModel;
265270
}
271+
272+
// We manually detect changes on all the children nodes when expansion
273+
// status changes; otherwise, the various attributes won't be updated.
274+
expansionModel.changed
275+
.pipe(withLatestFrom(this._nodes), takeUntil(this._onDestroy))
276+
.subscribe(([changes, nodes]) => {
277+
for (const added of changes.added) {
278+
nodes.get(added)?._changeDetectorRef.detectChanges();
279+
}
280+
for (const removed of changes.removed) {
281+
nodes.get(removed)?._changeDetectorRef.detectChanges();
282+
}
283+
});
266284
}
267285

268286
ngOnDestroy() {
@@ -986,13 +1004,21 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
9861004
return this._tree._getLevel(this._data) ?? this._parentNodeAriaLevel;
9871005
}
9881006

1007+
/** Determines if the tree node is expandable. */
1008+
_isExpandable(): boolean {
1009+
if (typeof this._tree.treeControl?.isExpandable === 'function') {
1010+
return this._tree.treeControl.isExpandable(this._data);
1011+
}
1012+
return this.isExpandable;
1013+
}
1014+
9891015
/**
9901016
* Determines the value for `aria-expanded`.
9911017
*
9921018
* For non-expandable nodes, this is `null`.
9931019
*/
9941020
_getAriaExpanded(): string | null {
995-
if (!this.isExpandable) {
1021+
if (!this._isExpandable()) {
9961022
return null;
9971023
}
9981024
return String(this.isExpanded);
@@ -1016,7 +1042,11 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
10161042
return this._tree._getPositionInSet(this._data);
10171043
}
10181044

1019-
constructor(protected _elementRef: ElementRef<HTMLElement>, protected _tree: CdkTree<T, K>) {
1045+
constructor(
1046+
protected _elementRef: ElementRef<HTMLElement>,
1047+
protected _tree: CdkTree<T, K>,
1048+
public _changeDetectorRef: ChangeDetectorRef,
1049+
) {
10201050
CdkTreeNode.mostRecentTreeNode = this as CdkTreeNode<T, K>;
10211051
this.role = 'treeitem';
10221052
}

src/material/tree/node.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import {
1717
AfterContentInit,
1818
Attribute,
19+
ChangeDetectorRef,
1920
Directive,
2021
ElementRef,
2122
Input,
@@ -83,9 +84,10 @@ export class MatTreeNode<T, K = T>
8384
constructor(
8485
elementRef: ElementRef<HTMLElement>,
8586
tree: CdkTree<T, K>,
87+
changeDetectorRef: ChangeDetectorRef,
8688
@Attribute('tabindex') tabIndex: string,
8789
) {
88-
super(elementRef, tree);
90+
super(elementRef, tree, changeDetectorRef);
8991
this.tabIndex = Number(tabIndex) || 0;
9092
}
9193

@@ -165,9 +167,10 @@ export class MatNestedTreeNode<T, K = T>
165167
private elementRef: ElementRef<HTMLElement>,
166168
tree: CdkTree<T, K>,
167169
differs: IterableDiffers,
170+
changeDetectorRef: ChangeDetectorRef,
168171
@Attribute('tabindex') tabIndex: string,
169172
) {
170-
super(elementRef, tree, differs);
173+
super(elementRef, tree, changeDetectorRef, differs);
171174
this.tabIndex = Number(tabIndex) || 0;
172175
}
173176

src/material/tree/testing/shared.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,9 @@ class TreeHarnessTest {
276276
node => node.expandable,
277277
);
278278
flatTreeDataSource = new MatTreeFlatDataSource(this.flatTreeControl, this.treeFlattener);
279-
nestedTreeControl = new NestedTreeControl<Node>(node => node.children);
279+
nestedTreeControl = new NestedTreeControl<Node>(node => node.children, {
280+
isExpandable: node => !!node.children && node.children.length > 0,
281+
});
280282
nestedTreeDataSource = new MatTreeNestedDataSource<Node>();
281283

282284
constructor() {

0 commit comments

Comments
 (0)