Skip to content

Commit cc53322

Browse files
committed
fix(cdk/tree): warn if mixed node types are used within the same tree
Currently the tree somewhat works if a flat node and a nested node are used together, however they can break down depending on the data that is passed in. These changes add a warning that will tell users to use a consistent node type. Fixes angular#29927.
1 parent 02d703b commit cc53322

File tree

4 files changed

+24
-22
lines changed

4 files changed

+24
-22
lines changed

src/cdk/tree/nested-node.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
IterableDiffer,
1313
IterableDiffers,
1414
OnDestroy,
15-
OnInit,
1615
QueryList,
1716
inject,
1817
} from '@angular/core';
@@ -40,8 +39,9 @@ import {CdkTreeNode} from './tree';
4039
})
4140
export class CdkNestedTreeNode<T, K = T>
4241
extends CdkTreeNode<T, K>
43-
implements AfterContentInit, OnDestroy, OnInit
42+
implements AfterContentInit, OnDestroy
4443
{
44+
protected override _type: 'flat' | 'nested' = 'nested';
4545
protected _differs = inject(IterableDiffers);
4646

4747
/** Differ used to find the changes in the data provided by the data source. */
@@ -75,13 +75,6 @@ export class CdkNestedTreeNode<T, K = T>
7575
.subscribe(() => this.updateChildrenNodes());
7676
}
7777

78-
// This is a workaround for https://github.com/angular/angular/issues/23091
79-
// In aot mode, the lifecycle hooks from parent class are not called.
80-
override ngOnInit() {
81-
this._tree._setNodeTypeIfUnset('nested');
82-
super.ngOnInit();
83-
}
84-
8578
override ngOnDestroy() {
8679
this._clear();
8780
super.ngOnDestroy();

src/cdk/tree/tree.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,17 @@ export class CdkTree<T, K = T>
323323
* This will be called by the first node that's rendered in order for the tree
324324
* to determine what data transformations are required.
325325
*/
326-
_setNodeTypeIfUnset(nodeType: 'flat' | 'nested') {
327-
if (this._nodeType.value === null) {
328-
this._nodeType.next(nodeType);
326+
_setNodeTypeIfUnset(newType: 'flat' | 'nested') {
327+
const currentType = this._nodeType.value;
328+
329+
if (currentType === null) {
330+
this._nodeType.next(newType);
331+
} else if ((typeof ngDevMode === 'undefined' || ngDevMode) && currentType !== newType) {
332+
console.warn(
333+
`Tree is using conflicting node types which can cause unexpected behavior. ` +
334+
`Please use tree nodes of the same type (e.g. only flat or only nested). ` +
335+
`Current node type: "${currentType}", new node type "${newType}".`,
336+
);
329337
}
330338
}
331339

@@ -1169,6 +1177,7 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
11691177
_elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
11701178
protected _tree = inject<CdkTree<T, K>>(CdkTree);
11711179
protected _tabindex: number | null = -1;
1180+
protected readonly _type: 'flat' | 'nested' = 'flat';
11721181

11731182
/**
11741183
* The role of the tree node.
@@ -1368,10 +1377,8 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
13681377
map(() => this.isExpanded),
13691378
distinctUntilChanged(),
13701379
)
1371-
.subscribe(() => {
1372-
this._changeDetectorRef.markForCheck();
1373-
});
1374-
this._tree._setNodeTypeIfUnset('flat');
1380+
.subscribe(() => this._changeDetectorRef.markForCheck());
1381+
this._tree._setNodeTypeIfUnset(this._type);
13751382
this._tree._registerNode(this);
13761383
}
13771384

src/material/tree/testing/tree-harness.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,9 @@ interface ExampleFlatNode {
235235
</mat-tree>
236236
<mat-tree [dataSource]="nestedTreeDataSource" [treeControl]="nestedTreeControl">
237237
<!-- This is the tree node template for leaf nodes -->
238-
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
238+
<mat-nested-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
239239
{{node.name}}
240-
</mat-tree-node>
240+
</mat-nested-tree-node>
241241
<!-- This is the tree node template for expandable nodes -->
242242
<mat-nested-tree-node *matTreeNodeDef="let node; when: nestedTreeHasChild" isExpandable>
243243
<button matTreeNodeToggle>

tools/public_api_guard/cdk/tree.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export abstract class BaseTreeControl<T, K = T> implements TreeControl<T, K> {
5454
export const CDK_TREE_NODE_OUTLET_NODE: InjectionToken<{}>;
5555

5656
// @public
57-
export class CdkNestedTreeNode<T, K = T> extends CdkTreeNode<T, K> implements AfterContentInit, OnDestroy, OnInit {
57+
export class CdkNestedTreeNode<T, K = T> extends CdkTreeNode<T, K> implements AfterContentInit, OnDestroy {
5858
constructor(...args: unknown[]);
5959
protected _children: T[];
6060
protected _clear(): void;
@@ -64,9 +64,9 @@ export class CdkNestedTreeNode<T, K = T> extends CdkTreeNode<T, K> implements Af
6464
ngAfterContentInit(): void;
6565
// (undocumented)
6666
ngOnDestroy(): void;
67-
// (undocumented)
68-
ngOnInit(): void;
6967
nodeOutlet: QueryList<CdkTreeNodeOutlet>;
68+
// (undocumented)
69+
protected _type: 'flat' | 'nested';
7070
protected updateChildrenNodes(children?: T[]): void;
7171
// (undocumented)
7272
static ɵdir: i0.ɵɵDirectiveDeclaration<CdkNestedTreeNode<any, any>, "cdk-nested-tree-node", ["cdkNestedTreeNode"], {}, {}, ["nodeOutlet"], never, true, never>;
@@ -118,7 +118,7 @@ export class CdkTree<T, K = T> implements AfterContentChecked, AfterContentInit,
118118
_registerNode(node: CdkTreeNode<T, K>): void;
119119
renderNodeChanges(data: readonly T[], dataDiffer?: IterableDiffer<T>, viewContainer?: ViewContainerRef, parentData?: T): void;
120120
protected _sendKeydownToKeyManager(event: KeyboardEvent): void;
121-
_setNodeTypeIfUnset(nodeType: 'flat' | 'nested'): void;
121+
_setNodeTypeIfUnset(newType: 'flat' | 'nested'): void;
122122
toggle(dataNode: T): void;
123123
toggleDescendants(dataNode: T): void;
124124
trackBy: TrackByFunction<T>;
@@ -205,6 +205,8 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
205205
protected _tabindex: number | null;
206206
// (undocumented)
207207
protected _tree: CdkTree<T, K>;
208+
// (undocumented)
209+
protected readonly _type: 'flat' | 'nested';
208210
typeaheadLabel: string | null;
209211
unfocus(): void;
210212
// (undocumented)

0 commit comments

Comments
 (0)