From ff26e6ac16dd9eb92175e5b2d19a7e7f5a76a2e2 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 11 Nov 2024 18:41:37 +0100 Subject: [PATCH 1/2] 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 #29927. --- src/cdk/tree/nested-node.ts | 11 ++-------- src/cdk/tree/tree.ts | 21 ++++++++++++------- .../tree/testing/tree-harness.spec.ts | 4 ++-- tools/public_api_guard/cdk/tree.md | 10 +++++---- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/cdk/tree/nested-node.ts b/src/cdk/tree/nested-node.ts index 406c4d65e8bb..6d7a93b88264 100644 --- a/src/cdk/tree/nested-node.ts +++ b/src/cdk/tree/nested-node.ts @@ -12,7 +12,6 @@ import { IterableDiffer, IterableDiffers, OnDestroy, - OnInit, QueryList, inject, } from '@angular/core'; @@ -40,8 +39,9 @@ import {CdkTreeNode} from './tree'; }) export class CdkNestedTreeNode extends CdkTreeNode - implements AfterContentInit, OnDestroy, OnInit + implements AfterContentInit, OnDestroy { + protected override _type: 'flat' | 'nested' = 'nested'; protected _differs = inject(IterableDiffers); /** Differ used to find the changes in the data provided by the data source. */ @@ -75,13 +75,6 @@ export class CdkNestedTreeNode .subscribe(() => this.updateChildrenNodes()); } - // This is a workaround for https://github.com/angular/angular/issues/23091 - // In aot mode, the lifecycle hooks from parent class are not called. - override ngOnInit() { - this._tree._setNodeTypeIfUnset('nested'); - super.ngOnInit(); - } - override ngOnDestroy() { this._clear(); super.ngOnDestroy(); diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 603f6b9dee3e..c7daf68f9fd1 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -323,9 +323,17 @@ export class CdkTree * This will be called by the first node that's rendered in order for the tree * to determine what data transformations are required. */ - _setNodeTypeIfUnset(nodeType: 'flat' | 'nested') { - if (this._nodeType.value === null) { - this._nodeType.next(nodeType); + _setNodeTypeIfUnset(newType: 'flat' | 'nested') { + const currentType = this._nodeType.value; + + if (currentType === null) { + this._nodeType.next(newType); + } else if ((typeof ngDevMode === 'undefined' || ngDevMode) && currentType !== newType) { + console.warn( + `Tree is using conflicting node types which can cause unexpected behavior. ` + + `Please use tree nodes of the same type (e.g. only flat or only nested). ` + + `Current node type: "${currentType}", new node type "${newType}".`, + ); } } @@ -1169,6 +1177,7 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI _elementRef = inject>(ElementRef); protected _tree = inject>(CdkTree); protected _tabindex: number | null = -1; + protected readonly _type: 'flat' | 'nested' = 'flat'; /** * The role of the tree node. @@ -1368,10 +1377,8 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI map(() => this.isExpanded), distinctUntilChanged(), ) - .subscribe(() => { - this._changeDetectorRef.markForCheck(); - }); - this._tree._setNodeTypeIfUnset('flat'); + .subscribe(() => this._changeDetectorRef.markForCheck()); + this._tree._setNodeTypeIfUnset(this._type); this._tree._registerNode(this); } diff --git a/src/material/tree/testing/tree-harness.spec.ts b/src/material/tree/testing/tree-harness.spec.ts index a5dd4e17fd44..c281d4d28b4e 100644 --- a/src/material/tree/testing/tree-harness.spec.ts +++ b/src/material/tree/testing/tree-harness.spec.ts @@ -235,9 +235,9 @@ interface ExampleFlatNode { - + {{node.name}} - +