Skip to content

Commit 5ce7ca8

Browse files
committed
feat(tree): Add mat tree (#8458)
1 parent bf2bd8d commit 5ce7ca8

26 files changed

+612
-20
lines changed

src/cdk/tree/control/flat-tree-control.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('CdkFlatTreeControl', () => {
3939
expect(treeControl.expansionModel.selected.length)
4040
.toBe(2, 'Expect two dataNodes in expansionModel');
4141

42-
treeControl.collapse(seconNode);
42+
treeControl.collapse(secondNode);
4343

4444
expect(treeControl.isExpanded(secondNode))
4545
.toBeFalsy('Expect second node to be collapsed');

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import {Observable} from 'rxjs/Observable';
9-
import {first} from 'rxjs/operators';
9+
import {take} from 'rxjs/operators/take';
1010
import {BaseTreeControl} from './base-tree-control';
1111

1212
/** Nested tree control. Able to expand/collapse a subtree recursively for NestedNode type. */
@@ -40,7 +40,7 @@ export class NestedTreeControl<T> extends BaseTreeControl<T> {
4040
/** A helper function to get descendants recursively. */
4141
protected _getDescendants(descendants: T[], dataNode: T): void {
4242
descendants.push(dataNode);
43-
first.call(this.getChildren(dataNode)).subscribe(children => {
43+
this.getChildren(dataNode).pipe(take(1)).subscribe(children => {
4444
if (children && children.length > 0) {
4545
children.forEach((child: T) => this._getDescendants(descendants, child));
4646
}

src/cdk/tree/nested-node.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
OnDestroy,
1414
QueryList,
1515
} from '@angular/core';
16-
import {takeUntil} from 'rxjs/operators';
16+
import {takeUntil} from 'rxjs/operators/takeUntil';
1717
import {CdkTree} from './tree';
1818
import {CdkTreeNodeOutlet} from './outlet';
1919
import {CdkTreeNode} from './node';
@@ -58,14 +58,14 @@ export class CdkNestedTreeNode<T> extends CdkTreeNode<T> implements AfterContent
5858
}
5959

6060
ngAfterContentInit() {
61-
takeUntil.call(this._tree.treeControl.getChildren(this.data), this._destroyed)
61+
this._tree.treeControl.getChildren(this.data).pipe(takeUntil(this._destroyed))
6262
.subscribe(result => {
6363
// In case when nodeOutlet is not in the DOM when children changes, save it in the node
6464
// and add to nodeOutlet when it's available.
6565
this._children = result as T[];
6666
this._addChildrenNodes();
6767
});
68-
takeUntil.call(this.nodeOutlet.changes, this._destroyed)
68+
this.nodeOutlet.changes.pipe(takeUntil(this._destroyed))
6969
.subscribe((_) => this._addChildrenNodes());
7070
}
7171

src/cdk/tree/node.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
OnDestroy,
1414
TemplateRef
1515
} from '@angular/core';
16-
import {takeUntil} from 'rxjs/operators';
16+
import {takeUntil} from 'rxjs/operators/takeUntil';
1717
import {Subject} from 'rxjs/Subject';
1818
import {CdkTree} from './tree';
1919
import {getTreeControlFunctionsMissingError} from './tree-errors';
@@ -69,15 +69,14 @@ export class CdkTreeNodeDef<T> {
6969
host: {
7070
'[attr.role]': 'role',
7171
'class': 'cdk-tree-node',
72-
'tabindex': '0',
7372
},
7473
})
7574
export class CdkTreeNode<T> implements FocusableOption, OnDestroy {
7675
/**
7776
* The most recently created `CdkTreeNode`. We save it in static variable so we can retrieve it
7877
* in `CdkTree` and set the data to it.
7978
*/
80-
static mostRecentTreeNode: CdkTreeNode<any>;
79+
static mostRecentTreeNode: CdkTreeNode<any> | null = null;
8180

8281
/** Subject that emits when the component has been destroyed. */
8382
protected _destroyed = new Subject<void>();
@@ -118,7 +117,7 @@ export class CdkTreeNode<T> implements FocusableOption, OnDestroy {
118117
if (!this._tree.treeControl.getChildren) {
119118
throw getTreeControlFunctionsMissingError();
120119
}
121-
takeUntil.call(this._tree.treeControl.getChildren(this._data), this._destroyed)
120+
this._tree.treeControl.getChildren(this._data).pipe(takeUntil(this._destroyed))
122121
.subscribe(children => {
123122
this.role = children ? 'group' : 'treeitem';
124123
});

src/cdk/tree/padding.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {Directionality} from '@angular/cdk/bidi';
1010
import {coerceNumberProperty} from '@angular/cdk/coercion';
1111
import {Directive, ElementRef, Input, OnDestroy, Optional, Renderer2} from '@angular/core';
12-
import {takeUntil} from 'rxjs/operators';
12+
import {takeUntil} from 'rxjs/operators/takeUntil';
1313
import {Subject} from 'rxjs/Subject';
1414
import {CdkTreeNode} from './node';
1515
import {CdkTree} from './tree';
@@ -49,7 +49,7 @@ export class CdkTreeNodePadding<T> implements OnDestroy {
4949
@Optional() private _dir: Directionality) {
5050
this._setPadding();
5151
if (this._dir) {
52-
takeUntil.call(this._dir.change, this._destroyed).subscribe(() => this._setPadding());
52+
this._dir.change.pipe(takeUntil(this._destroyed)).subscribe(() => this._setPadding());
5353
}
5454
}
5555

src/cdk/tree/tree.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {CollectionViewer, DataSource} from '@angular/cdk/collections';
55
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
66
import {Observable} from 'rxjs/Observable';
77
import {combineLatest} from 'rxjs/observable/combineLatest';
8-
import {map} from 'rxjs/operators';
8+
import {map} from 'rxjs/operators/map';
99

1010
import {TreeControl} from './control/tree-control';
1111
import {FlatTreeControl} from './control/flat-tree-control';
@@ -54,7 +54,7 @@ describe('CdkTree', () => {
5454
it('with rendered dataNodes', () => {
5555
const nodes = getNodes(treeElement);
5656

57-
expect(nodes).not.toBe(undefined);
57+
expect(nodes).toBeDefined('Expect nodes to be defined');
5858
expect(nodes[0].classList).toContain('customNodeClass');
5959
});
6060

src/cdk/tree/tree.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
ViewEncapsulation,
2424
} from '@angular/core';
2525
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
26-
import {takeUntil} from 'rxjs/operators';
26+
import {takeUntil} from 'rxjs/operators/takeUntil';
2727
import {Subject} from 'rxjs/Subject';
2828
import {CdkTreeNodeDef, CdkTreeNode, CdkTreeNodeOutletContext} from './node';
2929
import {CdkTreeNodeOutlet} from './outlet';
@@ -155,7 +155,7 @@ export class CdkTree<T> implements CollectionViewer, OnInit, OnDestroy {
155155

156156
/** Set up a subscription for the data provided by the data source. */
157157
private _observeRenderChanges() {
158-
takeUntil.call(this.dataSource.connect(this), this._destroyed)
158+
this.dataSource.connect(this).pipe(takeUntil(this._destroyed))
159159
.subscribe(data => {
160160
this._data = data;
161161
this._renderNodeChanges(data);
@@ -191,7 +191,7 @@ export class CdkTree<T> implements CollectionViewer, OnInit, OnDestroy {
191191
if (this._nodeDefs.length == 1) { return this._nodeDefs.first; }
192192

193193
const nodeDef =
194-
this._nodeDefs.find(def => def.when && def.when(data, i)) || this._defaultNodeDef;
194+
this._nodeDefs.find(def => def.when && def.when(i, data)) || this._defaultNodeDef;
195195
if (!nodeDef) { throw getTreeMissingMatchingNodeDefError(); }
196196

197197
return nodeDef;
@@ -214,7 +214,9 @@ export class CdkTree<T> implements CollectionViewer, OnInit, OnDestroy {
214214
// Set the data to just created `CdkTreeNode`.
215215
// The `CdkTreeNode` created from `createEmbeddedView` will be saved in static variable
216216
// `mostRecentTreeNode`. We get it from static variable and pass the node data to it.
217-
CdkTreeNode.mostRecentTreeNode.data = nodeData;
217+
if (CdkTreeNode.mostRecentTreeNode) {
218+
CdkTreeNode.mostRecentTreeNode.data = nodeData;
219+
}
218220

219221
this._changeDetectorRef.detectChanges();
220222
}

src/demo-app/demo-material-module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
MatTabsModule,
3939
MatToolbarModule,
4040
MatTooltipModule,
41+
MatTreeModule,
4142
MatStepperModule,
4243
} from '@angular/material';
4344
import {MatNativeDateModule, MatRippleModule} from '@angular/material';
@@ -49,6 +50,7 @@ import {PlatformModule} from '@angular/cdk/platform';
4950
import {ObserversModule} from '@angular/cdk/observers';
5051
import {PortalModule} from '@angular/cdk/portal';
5152
import {CdkTableModule} from '@angular/cdk/table';
53+
import {CdkTreeModule} from '@angular/cdk/tree';
5254

5355
/**
5456
* NgModule that includes all Material modules that are required to serve the demo-app.
@@ -87,8 +89,10 @@ import {CdkTableModule} from '@angular/cdk/table';
8789
MatTabsModule,
8890
MatToolbarModule,
8991
MatTooltipModule,
92+
MatTreeModule,
9093
MatNativeDateModule,
9194
CdkTableModule,
95+
CdkTreeModule,
9296
A11yModule,
9397
BidiModule,
9498
CdkAccordionModule,

src/demo-app/system-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ System.config({
5757
'@angular/cdk/scrolling': 'dist/packages/cdk/scrolling/index.js',
5858
'@angular/cdk/stepper': 'dist/packages/cdk/stepper/index.js',
5959
'@angular/cdk/table': 'dist/packages/cdk/table/index.js',
60+
'@angular/cdk/tree': 'dist/packages/cdk/tree/index.js',
6061

6162
'@angular/material/autocomplete': 'dist/packages/material/autocomplete/index.js',
6263
'@angular/material/button': 'dist/packages/material/button/index.js',
@@ -90,6 +91,7 @@ System.config({
9091
'@angular/material/tabs': 'dist/packages/material/tabs/index.js',
9192
'@angular/material/toolbar': 'dist/packages/material/toolbar/index.js',
9293
'@angular/material/tooltip': 'dist/packages/material/tooltip/index.js',
94+
'@angular/material/tree': 'dist/packages/material/tree/index.js',
9395
},
9496
packages: {
9597
// Thirdparty barrels.

src/lib/core/theming/_all-theme.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
@import '../../tabs/tabs-theme';
2929
@import '../../toolbar/toolbar-theme';
3030
@import '../../tooltip/tooltip-theme';
31+
@import '../../tree/tree-theme';
3132
@import '../../snack-bar/snack-bar-theme';
3233
@import '../../form-field/form-field-theme';
3334

@@ -64,5 +65,6 @@
6465
@include mat-tabs-theme($theme);
6566
@include mat-toolbar-theme($theme);
6667
@include mat-tooltip-theme($theme);
68+
@include mat-tree-theme($theme);
6769
@include mat-snack-bar-theme($theme);
6870
}

src/lib/core/typography/_all-typography.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
@import '../../tabs/tabs-theme';
2727
@import '../../toolbar/toolbar-theme';
2828
@import '../../tooltip/tooltip-theme';
29+
@import '../../tree/tree-theme';
2930
@import '../../snack-bar/snack-bar-theme';
3031
@import '../option/option-theme';
3132
@import '../option/optgroup-theme';
@@ -66,6 +67,7 @@
6667
@include mat-tabs-typography($config);
6768
@include mat-toolbar-typography($config);
6869
@include mat-tooltip-typography($config);
70+
@include mat-tree-typography($config);
6971
@include mat-list-typography($config);
7072
@include mat-option-typography($config);
7173
@include mat-optgroup-typography($config);

src/lib/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ export * from '@angular/material/table';
3939
export * from '@angular/material/tabs';
4040
export * from '@angular/material/toolbar';
4141
export * from '@angular/material/tooltip';
42+
export * from '@angular/material/tree';

src/lib/tree/_tree-theme.scss

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@import '../core/theming/palette';
2+
@import '../core/theming/theming';
3+
@import '../core/typography/typography-utils';
4+
5+
@mixin mat-tree-theme($theme) {
6+
$background: map-get($theme, background);
7+
$foreground: map-get($theme, foreground);
8+
9+
.mat-tree {
10+
background: mat-color($background, 'card');
11+
}
12+
13+
.mat-tree-node {
14+
color: mat-color($foreground, text);
15+
}
16+
}
17+
18+
@mixin mat-tree-typography($config) {
19+
.mat-tree {
20+
font-family: mat-font-family($config);
21+
}
22+
23+
.mat-tree-node {
24+
font-weight: mat-font-weight($config, body-1);
25+
font-size: mat-font-size($config, body-1);
26+
}
27+
}

src/lib/tree/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './public-api';

src/lib/tree/node.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {
10+
ContentChildren,
11+
Directive,
12+
Input,
13+
QueryList
14+
} from '@angular/core';
15+
import {
16+
CdkNestedTreeNode,
17+
CdkTreeNodeDef,
18+
CdkTreeNode,
19+
} from '@angular/cdk/tree';
20+
import {MatTreeNodeOutlet} from './outlet';
21+
22+
/**
23+
* Wrapper for the CdkTree node with Material design styles.
24+
*/
25+
// TODO(tinayuangao): use mixinTabIndex
26+
@Directive({
27+
selector: 'mat-tree-node',
28+
exportAs: 'matTreeNode',
29+
host: {
30+
'[attr.role]': 'role',
31+
'class': 'mat-tree-node',
32+
'tabindex': '0',
33+
},
34+
providers: [{provide: CdkTreeNode, useExisting: MatTreeNode}]
35+
})
36+
export class MatTreeNode<T> extends CdkTreeNode<T> {
37+
@Input() role: 'treeitem' | 'group' = 'treeitem';
38+
}
39+
40+
/**
41+
* Wrapper for the CdkTree node definition with Material design styles.
42+
*/
43+
@Directive({
44+
selector: '[matTreeNodeDef]',
45+
inputs: [
46+
'when: matTreeNodeDefWhen'
47+
],
48+
providers: [{provide: CdkTreeNodeDef, useExisting: MatTreeNodeDef}]
49+
})
50+
export class MatTreeNodeDef<T> extends CdkTreeNodeDef<T> {
51+
@Input('matTreeNode') data: T;
52+
}
53+
54+
/**
55+
* Wrapper for the CdkTree nested node with Material design styles.
56+
*/
57+
@Directive({
58+
selector: 'mat-nested-tree-node',
59+
exportAs: 'matNestedTreeNode',
60+
host: {
61+
'[attr.role]': 'role',
62+
'class': 'mat-nested-tree-node',
63+
},
64+
providers: [
65+
{provide: CdkNestedTreeNode, useExisting: MatNestedTreeNode},
66+
{provide: CdkTreeNode, useExisting: MatNestedTreeNode}
67+
]
68+
})
69+
export class MatNestedTreeNode<T> extends CdkNestedTreeNode<T> {
70+
@Input('matNestedTreeNode') node: T;
71+
72+
@ContentChildren(MatTreeNodeOutlet) nodeOutlet: QueryList<MatTreeNodeOutlet>;
73+
}

src/lib/tree/outlet.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {CdkTreeNodeOutlet} from '@angular/cdk/tree';
9+
import {
10+
Directive,
11+
ViewContainerRef,
12+
} from '@angular/core';
13+
14+
/**
15+
* Outlet for nested CdkNode. Put `[matTreeNodeOutlet]` on a tag to place children dataNodes
16+
* inside the outlet.
17+
*/
18+
@Directive({
19+
selector: '[matTreeNodeOutlet]'
20+
})
21+
export class MatTreeNodeOutlet implements CdkTreeNodeOutlet {
22+
constructor(public viewContainer: ViewContainerRef) {}
23+
}

0 commit comments

Comments
 (0)