Skip to content

Commit 23f71cc

Browse files
crisbetojelbourn
authored andcommitted
refactor(tree): avoid improper query usage (#16540)
The way things are set up at the moment we have a `CdkNestedTreeNode` which uses a `ContentChildren` to find a `CdkTreeNodeOutlet` so that it knows where to render its content. On top of this we have a `MatNestedTreeNode` which extends `CdkNestedTreeNode` and defines it's own query at the same property, but looking for a `MatTreeNodeOutlet`. With this setup it means that both `CdkNestedTreeNode` and `MatNestedTreeNode` are trying to write to the same `nodeOutlet` property and it's by pure coincidence that it works. This is very fragile and it isn't guaranteed to work in future versions of the framework. These changes fix the issue by removing the query from the `MatNestedTreeNode` and providing the `MatTreeNodeOutlet` as a `CdkTreeNodeOutlet` so that the query on the `CdkNestedTreeNode` can pick it up.
1 parent 0d4269b commit 23f71cc

File tree

3 files changed

+25
-22
lines changed

3 files changed

+25
-22
lines changed

src/material/tree/node.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ import {
1616
import {
1717
AfterContentInit,
1818
Attribute,
19-
ContentChildren,
2019
Directive,
2120
ElementRef,
2221
Input,
2322
IterableDiffers,
2423
OnDestroy,
25-
QueryList,
2624
} from '@angular/core';
2725
import {
2826
CanDisable,
@@ -32,16 +30,11 @@ import {
3230
mixinDisabled,
3331
mixinTabIndex,
3432
} from '@angular/material/core';
35-
36-
import {MatTreeNodeOutlet} from './outlet';
33+
import {coerceBooleanProperty} from '@angular/cdk/coercion';
3734

3835
const _MatTreeNodeMixinBase: HasTabIndexCtor & CanDisableCtor & typeof CdkTreeNode =
3936
mixinTabIndex(mixinDisabled(CdkTreeNode));
4037

41-
const _MatNestedTreeNodeMixinBase:
42-
HasTabIndexCtor & CanDisableCtor & typeof CdkNestedTreeNode =
43-
mixinTabIndex(mixinDisabled(CdkNestedTreeNode));
44-
4538
/**
4639
* Wrapper for the CdkTree node with Material design styles.
4740
*/
@@ -95,31 +88,36 @@ export class MatTreeNodeDef<T> extends CdkTreeNodeDef<T> {
9588
'[attr.role]': 'role',
9689
'class': 'mat-nested-tree-node',
9790
},
98-
inputs: ['disabled', 'tabIndex'],
9991
providers: [
10092
{provide: CdkNestedTreeNode, useExisting: MatNestedTreeNode},
10193
{provide: CdkTreeNode, useExisting: MatNestedTreeNode},
10294
{provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: MatNestedTreeNode}
10395
]
10496
})
105-
export class MatNestedTreeNode<T> extends _MatNestedTreeNodeMixinBase<T> implements
106-
AfterContentInit, CanDisable, HasTabIndex, OnDestroy {
97+
export class MatNestedTreeNode<T> extends CdkNestedTreeNode<T> implements AfterContentInit,
98+
OnDestroy {
10799
@Input('matNestedTreeNode') node: T;
108100

109-
/** The children node placeholder. */
110-
@ContentChildren(MatTreeNodeOutlet, {
111-
// We need to use `descendants: true`, because Ivy will no longer match
112-
// indirect descendants if it's left as false.
113-
descendants: true
114-
})
115-
nodeOutlet: QueryList<MatTreeNodeOutlet>;
101+
/** Whether the node is disabled. */
102+
@Input()
103+
get disabled() { return this._disabled; }
104+
set disabled(value: any) { this._disabled = coerceBooleanProperty(value); }
105+
private _disabled = false;
106+
107+
/** Tabindex for the node. */
108+
@Input()
109+
get tabIndex(): number { return this.disabled ? -1 : this._tabIndex; }
110+
set tabIndex(value: number) {
111+
// If the specified tabIndex value is null or undefined, fall back to the default value.
112+
this._tabIndex = value != null ? value : 0;
113+
}
114+
private _tabIndex: number;
116115

117116
constructor(protected _elementRef: ElementRef<HTMLElement>,
118117
protected _tree: CdkTree<T>,
119118
protected _differs: IterableDiffers,
120119
@Attribute('tabindex') tabIndex: string) {
121120
super(_elementRef, _tree, _differs);
122-
123121
this.tabIndex = Number(tabIndex) || 0;
124122
}
125123

src/material/tree/outlet.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import {
1818
* inside the outlet.
1919
*/
2020
@Directive({
21-
selector: '[matTreeNodeOutlet]'
21+
selector: '[matTreeNodeOutlet]',
22+
providers: [{
23+
provide: CdkTreeNodeOutlet,
24+
useExisting: MatTreeNodeOutlet
25+
}]
2226
})
2327
export class MatTreeNodeOutlet implements CdkTreeNodeOutlet {
2428
constructor(

tools/public_api_guard/material/tree.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
export declare class MatNestedTreeNode<T> extends _MatNestedTreeNodeMixinBase<T> implements AfterContentInit, CanDisable, HasTabIndex, OnDestroy {
1+
export declare class MatNestedTreeNode<T> extends CdkNestedTreeNode<T> implements AfterContentInit, OnDestroy {
22
protected _differs: IterableDiffers;
33
protected _elementRef: ElementRef<HTMLElement>;
44
protected _tree: CdkTree<T>;
5+
disabled: any;
56
node: T;
6-
nodeOutlet: QueryList<MatTreeNodeOutlet>;
7+
tabIndex: number;
78
constructor(_elementRef: ElementRef<HTMLElement>, _tree: CdkTree<T>, _differs: IterableDiffers, tabIndex: string);
89
ngAfterContentInit(): void;
910
ngOnDestroy(): void;

0 commit comments

Comments
 (0)