Skip to content

Commit 8aa35c6

Browse files
authored
refactor(cdk-experimental/tree): use explicit inputs for passing dependencies (#31654)
1 parent 8da079d commit 8aa35c6

File tree

4 files changed

+137
-171
lines changed

4 files changed

+137
-171
lines changed

src/cdk-experimental/tree/tree.spec.ts

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Component, signal} from '@angular/core';
2+
import {NgTemplateOutlet} from '@angular/common';
23
import {ComponentFixture, TestBed} from '@angular/core/testing';
34
import {By} from '@angular/platform-browser';
45
import {Direction} from '@angular/cdk/bidi';
@@ -1336,63 +1337,42 @@ interface TestTreeNode<V = string> {
13361337
[(value)]="value"
13371338
[nav]="nav()"
13381339
[currentType]="currentType()"
1340+
#tree="cdkTree"
13391341
>
13401342
@for (node of nodes(); track node.value) {
1341-
<li
1342-
cdkTreeItem
1343-
[value]="node.value"
1344-
[label]="node.label"
1345-
[disabled]="!!node.disabled"
1346-
[attr.data-value]="node.value"
1347-
>
1348-
{{ node.label }}
1349-
@if (node.children !== undefined && node.children!.length > 0) {
1350-
<ul
1351-
cdkTreeItemGroup
1352-
[value]="node.value"
1353-
[preserveContent]="!!node.preserveContent"
1354-
[attr.data-group-for]="node.value">
1355-
<ng-template cdkTreeItemGroupContent>
1356-
@for (node of node.children; track node.value) {
1357-
<li
1358-
cdkTreeItem
1359-
[value]="node.value"
1360-
[label]="node.label"
1361-
[disabled]="!!node.disabled"
1362-
[attr.data-value]="node.value"
1363-
>
1364-
{{ node.label }}
1365-
@if (node.children !== undefined && node.children!.length > 0) {
1366-
<ul
1367-
cdkTreeItemGroup
1368-
[value]="node.value"
1369-
[preserveContent]="!!node.preserveContent"
1370-
[attr.data-group-for]="node.value">
1371-
<ng-template cdkTreeItemGroupContent>
1372-
@for (node of node.children; track node.value) {
1373-
<li
1374-
cdkTreeItem
1375-
[value]="node.value"
1376-
[label]="node.label"
1377-
[disabled]="!!node.disabled"
1378-
[attr.data-value]="node.value"
1379-
>
1380-
{{ node.label }}
1381-
</li>
1382-
}
1383-
</ng-template>
1384-
</ul>
1385-
}
1386-
</li>
1387-
}
1388-
</ng-template>
1389-
</ul>
1390-
}
1391-
</li>
1343+
<ng-template [ngTemplateOutlet]="nodeTemplate" [ngTemplateOutletContext]="{ node: node, parent: tree }" />
13921344
}
13931345
</ul>
1346+
1347+
<ng-template #nodeTemplate let-node="node" let-parent="parent">
1348+
<li
1349+
cdkTreeItem
1350+
[value]="node.value"
1351+
[label]="node.label"
1352+
[disabled]="!!node.disabled"
1353+
[parent]="parent"
1354+
[attr.data-value]="node.value"
1355+
#treeItem="cdkTreeItem"
1356+
>
1357+
{{ node.label }}
1358+
@if (node.children !== undefined && node.children!.length > 0) {
1359+
<ul
1360+
cdkTreeItemGroup
1361+
[ownedBy]="treeItem"
1362+
[preserveContent]="!!node.preserveContent"
1363+
[attr.data-group-for]="node.value"
1364+
#group="cdkTreeItemGroup">
1365+
<ng-template cdkTreeItemGroupContent>
1366+
@for (node of node.children; track node.value) {
1367+
<ng-template [ngTemplateOutlet]="nodeTemplate" [ngTemplateOutletContext]="{ node: node, parent: group }" />
1368+
}
1369+
</ng-template>
1370+
</ul>
1371+
}
1372+
</li>
1373+
</ng-template>
13941374
`,
1395-
imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent],
1375+
imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
13961376
})
13971377
class TestTreeComponent {
13981378
nodes = signal<TestTreeNode[]>([

src/cdk-experimental/tree/tree.ts

Lines changed: 61 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ export class CdkTree<V> {
7979
/** All CdkTreeItem instances within this tree. */
8080
private readonly _unorderedItems = signal(new Set<CdkTreeItem<V>>());
8181

82-
/** All CdkGroup instances within this tree. */
83-
readonly unorderedGroups = signal(new Set<CdkTreeItemGroup<V>>());
84-
8582
/** Orientation of the tree. */
8683
readonly orientation = input<'vertical' | 'horizontal'>('vertical');
8784

@@ -144,28 +141,14 @@ export class CdkTree<V> {
144141
this._hasFocused.set(true);
145142
}
146143

147-
register(child: CdkTreeItemGroup<V> | CdkTreeItem<V>) {
148-
if (child instanceof CdkTreeItemGroup) {
149-
this.unorderedGroups().add(child);
150-
this.unorderedGroups.set(new Set(this.unorderedGroups()));
151-
}
152-
153-
if (child instanceof CdkTreeItem) {
154-
this._unorderedItems().add(child);
155-
this._unorderedItems.set(new Set(this._unorderedItems()));
156-
}
144+
register(child: CdkTreeItem<V>) {
145+
this._unorderedItems().add(child);
146+
this._unorderedItems.set(new Set(this._unorderedItems()));
157147
}
158148

159-
deregister(child: CdkTreeItemGroup<V> | CdkTreeItem<V>) {
160-
if (child instanceof CdkTreeItemGroup) {
161-
this.unorderedGroups().delete(child);
162-
this.unorderedGroups.set(new Set(this.unorderedGroups()));
163-
}
164-
165-
if (child instanceof CdkTreeItem) {
166-
this._unorderedItems().delete(child);
167-
this._unorderedItems.set(new Set(this._unorderedItems()));
168-
}
149+
unregister(child: CdkTreeItem<V>) {
150+
this._unorderedItems().delete(child);
151+
this._unorderedItems.set(new Set(this._unorderedItems()));
169152
}
170153
}
171154

@@ -185,7 +168,7 @@ export class CdkTree<V> {
185168
'[attr.aria-current]': 'pattern.current()',
186169
'[attr.aria-disabled]': 'pattern.disabled()',
187170
'[attr.aria-level]': 'pattern.level()',
188-
'[attr.aria-owns]': 'group()?.id',
171+
'[attr.aria-owns]': 'ownsId()',
189172
'[attr.aria-setsize]': 'pattern.setsize()',
190173
'[attr.aria-posinset]': 'pattern.posinset()',
191174
'[attr.tabindex]': 'pattern.tabindex()',
@@ -199,29 +182,21 @@ export class CdkTreeItem<V> implements OnInit, OnDestroy, HasElement {
199182
/** A unique identifier for the tree item. */
200183
private readonly _id = inject(_IdGenerator).getId('cdk-tree-item-');
201184

202-
/** The top level CdkTree. */
203-
private readonly _tree = inject(CdkTree<V>);
204-
205-
/** The parent CdkTreeItem. */
206-
private readonly _treeItem = inject(CdkTreeItem<V>, {optional: true, skipSelf: true});
207-
208-
/** The parent CdkGroup, if any. */
209-
private readonly _parentGroup = inject(CdkTreeItemGroup<V>, {optional: true});
185+
/** The owned tree item group. */
186+
private readonly _group = signal<CdkTreeItemGroup<V> | undefined>(undefined);
210187

211-
/** The top level TreePattern. */
212-
private readonly _treePattern = computed(() => this._tree.pattern);
213-
214-
/** The parent TreeItemPattern. */
215-
private readonly _parentPattern: Signal<TreeItemPattern<V> | TreePattern<V>> = computed(
216-
() => this._treeItem?.pattern ?? this._treePattern(),
217-
);
188+
/** The id of the owned group. */
189+
readonly ownsId = computed(() => this._group()?.id);
218190

219191
/** The host native element. */
220192
readonly element = computed(() => this._elementRef.nativeElement);
221193

222194
/** The value of the tree item. */
223195
readonly value = input.required<V>();
224196

197+
/** The parent tree root or tree item group. */
198+
readonly parent = input.required<CdkTree<V> | CdkTreeItemGroup<V>>();
199+
225200
/** Whether the tree item is disabled. */
226201
readonly disabled = input(false, {transform: booleanAttribute});
227202

@@ -231,46 +206,61 @@ export class CdkTreeItem<V> implements OnInit, OnDestroy, HasElement {
231206
/** Search term for typeahead. */
232207
readonly searchTerm = computed(() => this.label() ?? this.element().textContent);
233208

234-
/** Manual group assignment. */
235-
readonly group = signal<CdkTreeItemGroup<V> | undefined>(undefined);
209+
/** The tree root. */
210+
readonly tree: Signal<CdkTree<V>> = computed(() => {
211+
if (this.parent() instanceof CdkTree) {
212+
return this.parent() as CdkTree<V>;
213+
}
214+
return (this.parent() as CdkTreeItemGroup<V>).ownedBy().tree();
215+
});
236216

237217
/** The UI pattern for this item. */
238-
readonly pattern: TreeItemPattern<V> = new TreeItemPattern<V>({
239-
...this,
240-
id: () => this._id,
241-
tree: this._treePattern,
242-
parent: this._parentPattern,
243-
children: computed(
244-
() =>
245-
this.group()
246-
?.children()
247-
.map(item => (item as CdkTreeItem<V>).pattern) ?? [],
248-
),
249-
hasChildren: computed(() => !!this.group()),
250-
});
218+
pattern: TreeItemPattern<V>;
251219

252220
constructor() {
253-
afterRenderEffect(() => {
254-
const group = [...this._tree.unorderedGroups()].find(group => group.value() === this.value());
255-
if (group) {
256-
this.group.set(group);
257-
}
258-
});
259-
260221
// Updates the visibility of the owned group.
261222
afterRenderEffect(() => {
262-
this.group()?.visible.set(this.pattern.expanded());
223+
this._group()?.visible.set(this.pattern.expanded());
263224
});
264225
}
265226

266227
ngOnInit() {
267-
this._tree.register(this);
268-
this._parentGroup?.register(this);
228+
this.parent().register(this);
229+
this.tree().register(this);
230+
231+
const treePattern = computed(() => this.tree().pattern);
232+
const parentPattern = computed(() => {
233+
if (this.parent() instanceof CdkTree) {
234+
return treePattern();
235+
}
236+
return (this.parent() as CdkTreeItemGroup<V>).ownedBy().pattern;
237+
});
238+
this.pattern = new TreeItemPattern<V>({
239+
...this,
240+
id: () => this._id,
241+
tree: treePattern,
242+
parent: parentPattern,
243+
children: computed(
244+
() =>
245+
this._group()
246+
?.children()
247+
.map(item => (item as CdkTreeItem<V>).pattern) ?? [],
248+
),
249+
hasChildren: computed(() => !!this._group()),
250+
});
269251
}
270252

271253
ngOnDestroy() {
272-
this._tree.deregister(this);
273-
this._parentGroup?.deregister(this);
254+
this.parent().unregister(this);
255+
this.tree().unregister(this);
256+
}
257+
258+
register(group: CdkTreeItemGroup<V>) {
259+
this._group.set(group);
260+
}
261+
262+
unregister() {
263+
this._group.set(undefined);
274264
}
275265
}
276266

@@ -300,9 +290,6 @@ export class CdkTreeItemGroup<V> implements OnInit, OnDestroy, HasElement {
300290
/** The DeferredContentAware host directive. */
301291
private readonly _deferredContentAware = inject(DeferredContentAware);
302292

303-
/** The top level CdkTree. */
304-
private readonly _tree = inject(CdkTree<V>);
305-
306293
/** All groupable items that are descendants of the group. */
307294
private readonly _unorderedItems = signal(new Set<CdkTreeItem<V>>());
308295

@@ -318,8 +305,8 @@ export class CdkTreeItemGroup<V> implements OnInit, OnDestroy, HasElement {
318305
/** Child items within this group. */
319306
readonly children = computed(() => [...this._unorderedItems()].sort(sortDirectives));
320307

321-
/** Identifier for matching the group owner. */
322-
readonly value = input.required<V>();
308+
/** Tree item that owns the group. */
309+
readonly ownedBy = input.required<CdkTreeItem<V>>();
323310

324311
constructor() {
325312
// Connect the group's hidden state to the DeferredContentAware's visibility.
@@ -329,19 +316,19 @@ export class CdkTreeItemGroup<V> implements OnInit, OnDestroy, HasElement {
329316
}
330317

331318
ngOnInit() {
332-
this._tree.register(this);
319+
this.ownedBy().register(this);
333320
}
334321

335322
ngOnDestroy() {
336-
this._tree.deregister(this);
323+
this.ownedBy().unregister();
337324
}
338325

339326
register(child: CdkTreeItem<V>) {
340327
this._unorderedItems().add(child);
341328
this._unorderedItems.set(new Set(this._unorderedItems()));
342329
}
343330

344-
deregister(child: CdkTreeItem<V>) {
331+
unregister(child: CdkTreeItem<V>) {
345332
this._unorderedItems().delete(child);
346333
this._unorderedItems.set(new Set(this._unorderedItems()));
347334
}

src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.html

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,44 @@
5050
>
5151
@if (nav.value) {
5252
@for (node of treeData; track node) {
53-
<example-nav-node [node]="node" />
53+
<ng-template [ngTemplateOutlet]="navNodeTemplate" [ngTemplateOutletContext]="{ node: node, parent: tree }" />
5454
}
5555
} @else {
5656
@for (node of treeData; track node) {
57-
<example-node [node]="node" />
57+
<example-node [node]="node" [parent]="tree" />
5858
}
5959
}
6060
</ul>
61+
62+
<ng-template #navNodeTemplate let-node="node" let-parent="parent">
63+
<li class="example-tree-item">
64+
<a
65+
cdkTreeItem
66+
class="example-tree-item-content example-selectable example-stateful"
67+
[value]="node.value"
68+
[label]="node.label || node.value"
69+
[disabled]="node.disabled"
70+
[parent]="parent"
71+
#treeItem="cdkTreeItem"
72+
href="#{{node.value}}"
73+
(click)="$event.preventDefault()"
74+
>
75+
<mat-icon class="example-tree-item-icon" aria-hidden="true">
76+
@if (treeItem.pattern.expandable()) {
77+
{{ treeItem.pattern.expanded() ? 'expand_less' : 'expand_more' }}
78+
}
79+
</mat-icon>
80+
{{ node.label }}
81+
</a>
82+
83+
@if (node.children !== undefined && node.children!.length > 0) {
84+
<ul cdkTreeItemGroup [ownedBy]="treeItem" #group="cdkTreeItemGroup">
85+
<ng-template cdkTreeItemGroupContent>
86+
@for (child of node.children; track child) {
87+
<ng-template [ngTemplateOutlet]="navNodeTemplate" [ngTemplateOutletContext]="{ node: child, parent: group }" />
88+
}
89+
</ng-template>
90+
</ul>
91+
}
92+
</li>
93+
</ng-template>

0 commit comments

Comments
 (0)