Skip to content

Commit 7ffcc31

Browse files
committed
feat(tree): add data sources for tree (#9036)
1 parent 22bdfdc commit 7ffcc31

File tree

7 files changed

+940
-123
lines changed

7 files changed

+940
-123
lines changed

src/cdk-experimental/tree/padding.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class CdkTreeNodePadding<T> implements OnDestroy {
3131
this._level = coerceNumberProperty(value);
3232
this._setPadding();
3333
}
34+
get level(): number { return this._level; }
3435
_level: number;
3536

3637
/** The indent for each level. Default number 40px from material design menu sub-menu spec. */
@@ -40,6 +41,7 @@ export class CdkTreeNodePadding<T> implements OnDestroy {
4041
this._indent = coerceNumberProperty(value);
4142
this._setPadding();
4243
}
44+
get indent(): number { return this._indent; }
4345
_indent: number = 40;
4446

4547
constructor(private _treeNode: CdkTreeNode<T>,

src/demo-app/tree/tree-demo.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
import {Component} from '@angular/core';
1010
import {FlatTreeControl, NestedTreeControl} from '@angular/cdk-experimental/tree';
11+
import {MatTreeFlattener, MatTreeFlatDataSource, MatTreeNestedDataSource} from '@angular/material-experimental/tree';
1112
import {of as ofObservable} from 'rxjs/observable/of';
1213

1314
import {JsonNode, JsonDatabase} from './json-database';
14-
import {FlatDataSource, JsonFlatNode} from './flat-data-source';
15-
import {JsonNestedDataSource} from './nested-data-source';
15+
import {JsonFlatNode} from './flat-data-source';
1616

1717

1818
@Component({
@@ -28,22 +28,41 @@ export class TreeDemo {
2828
// Nested tree control
2929
nestedTreeControl: NestedTreeControl<JsonNode>;
3030

31+
treeFlattener: MatTreeFlattener<JsonNode, JsonFlatNode>;
32+
3133
// Flat tree data source
32-
dataSource: FlatDataSource;
34+
dataSource: MatTreeFlatDataSource<JsonNode, JsonFlatNode>;
3335

3436
// Nested tree data source
35-
nestedDataSource: JsonNestedDataSource;
37+
nestedDataSource: MatTreeNestedDataSource<JsonNode>;
3638

3739
constructor(database: JsonDatabase) {
40+
this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel,
41+
this.isExpandable, this.getChildren);
3842
// For flat tree
3943
this.treeControl = new FlatTreeControl<JsonFlatNode>(this.getLevel, this.isExpandable);
40-
this.dataSource = new FlatDataSource(database, this.treeControl);
44+
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
4145

4246
// For nested tree
4347
this.nestedTreeControl = new NestedTreeControl<JsonNode>(this.getChildren);
44-
this.nestedDataSource = new JsonNestedDataSource(database);
48+
this.nestedDataSource = new MatTreeNestedDataSource();
49+
50+
database.dataChange.subscribe(data => {
51+
console.log(`datachanges in demo`)
52+
this.dataSource.data = data;
53+
this.nestedDataSource.data = data;
54+
})
4555
}
4656

57+
transformer = (node: JsonNode, level: number) => {
58+
let flatNode = new JsonFlatNode();
59+
flatNode.key = node.key;
60+
flatNode.value = node.value;
61+
flatNode.level = level;
62+
flatNode.expandable = !!node.children;
63+
return flatNode;
64+
};
65+
4766
getLevel = (node: JsonFlatNode) => { return node.level; };
4867

4968
isExpandable = (node: JsonFlatNode) => { return node.expandable; };
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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 {CollectionViewer, DataSource} from '@angular/cdk/collections';
10+
import {FlatTreeControl, TreeControl} from '@angular/cdk-experimental/tree';
11+
import {Observable} from 'rxjs/Observable';
12+
import {merge} from 'rxjs/observable/merge';
13+
import {map} from 'rxjs/operators/map';
14+
import {take} from 'rxjs/operators/take';
15+
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
16+
17+
/**
18+
* Tree flattener to convert a normal type of node to node with children & level information.
19+
* Transform nested nodes of type `T` to flattened nodes of type `F`.
20+
*
21+
* For example, the input data of type `T` is nested, and contains its children data:
22+
* SomeNode: {
23+
* key: 'Fruits',
24+
* children: [
25+
* NodeOne: {
26+
* key: 'Apple',
27+
* },
28+
* NodeTwo: {
29+
* key: 'Pear',
30+
* }
31+
* ]
32+
* }
33+
* After flattener flatten the tree, the structure will become
34+
* SomeNode: {
35+
* key: 'Fruits',
36+
* expandable: true,
37+
* level: 1
38+
* },
39+
* NodeOne: {
40+
* key: 'Apple',
41+
* expandable: false,
42+
* level: 2
43+
* },
44+
* NodeTwo: {
45+
* key: 'Pear',
46+
* expandable: false,
47+
* level: 2
48+
* }
49+
* and the output flattened type is `F` with additional information.
50+
*/
51+
export class MatTreeFlattener<T, F> {
52+
53+
constructor(public transformFunction: (node: T, level: number) => F,
54+
public getLevel: (node: F) => number,
55+
public isExpandable: (node: F) => boolean,
56+
public getChildren: (node: T) => Observable<T[]>) {}
57+
58+
_flattenNode(node: T, level: number,
59+
resultNodes: F[], parentMap: boolean[]): F[] {
60+
const flatNode = this.transformFunction(node, level);
61+
resultNodes.push(flatNode);
62+
63+
if (this.isExpandable(flatNode)) {
64+
this.getChildren(node).pipe(take(1)).subscribe(children => {
65+
children.forEach((child, index) => {
66+
let childParentMap: boolean[] = parentMap.slice();
67+
childParentMap.push(index != children.length - 1);
68+
this._flattenNode(child, level + 1, resultNodes, childParentMap);
69+
});
70+
});
71+
}
72+
return resultNodes;
73+
}
74+
75+
/**
76+
* Flatten a list of node type T to flattened version of node F.
77+
* Please note that type T may be nested, and the length of `structuredData` may be different
78+
* from that of returned list `F[]`.
79+
*/
80+
flattenNodes(structuredData: T[]): F[] {
81+
let resultNodes: F[] = [];
82+
structuredData.forEach(node => this._flattenNode(node, 0, resultNodes, []));
83+
return resultNodes;
84+
}
85+
86+
/**
87+
* Expand flattened node with current expansion status.
88+
* The returned list may have different length.
89+
*/
90+
expandFlattenedNodes(nodes: F[], treeControl: TreeControl<F>): F[] {
91+
let results: F[] = [];
92+
let currentExpand: boolean[] = [];
93+
currentExpand[0] = true;
94+
95+
nodes.forEach((node) => {
96+
let expand = true;
97+
for (let i = 0; i <= this.getLevel(node); i++) {
98+
expand = expand && currentExpand[i];
99+
}
100+
if (expand) {
101+
results.push(node);
102+
}
103+
if (this.isExpandable(node)) {
104+
currentExpand[this.getLevel(node) + 1] = treeControl.isExpanded(node);
105+
}
106+
});
107+
return results;
108+
}
109+
}
110+
111+
112+
/**
113+
* Data source for flat tree.
114+
* The data source need to handle expansion/collapsion of the tree node and change the data feed
115+
* to `MatTree`.
116+
* The nested tree nodes of type `T` are flattened through `MatTreeFlattener`, and converted
117+
* to type `F` for `MatTree` to consume.
118+
*/
119+
export class MatTreeFlatDataSource<T, F> implements DataSource<F> {
120+
_flattenedData = new BehaviorSubject<F[]>([]);
121+
122+
_expandedData = new BehaviorSubject<F[]>([]);
123+
124+
_data: BehaviorSubject<T[]>;
125+
get data() { return this._data.value; }
126+
set data(value: T[]) {
127+
this._data.next(value);
128+
this._flattenedData.next(this.treeFlattener.flattenNodes(this.data));
129+
this.treeControl.dataNodes = this._flattenedData.value;
130+
}
131+
132+
constructor(private treeControl: FlatTreeControl<F>,
133+
private treeFlattener: MatTreeFlattener<T, F>,
134+
initialData: T[] = []) {
135+
this._data = new BehaviorSubject<T[]>(initialData);
136+
}
137+
138+
connect(collectionViewer: CollectionViewer): Observable<F[]> {
139+
return merge([
140+
collectionViewer.viewChange,
141+
this.treeControl.expansionModel.onChange,
142+
this._flattenedData])
143+
.pipe(map(() => {
144+
this._expandedData.next(
145+
this.treeFlattener.expandFlattenedNodes(this._flattenedData.value, this.treeControl));
146+
return this._expandedData.value;
147+
}));
148+
}
149+
150+
disconnect() {
151+
}
152+
}
153+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 {CollectionViewer, DataSource} from '@angular/cdk/collections';
10+
import {Observable} from 'rxjs/Observable';
11+
import {merge} from 'rxjs/observable/merge';
12+
import {map} from 'rxjs/operators/map';
13+
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
14+
15+
/**
16+
* Data source for nested tree.
17+
*
18+
* The data source for nested tree doesn't have to consider node flattener, or the way to expand
19+
* or collapse. The expansion/collapsion will be handled by TreeControl and each non-leaf node.
20+
*/
21+
export class MatTreeNestedDataSource<T> implements DataSource<T> {
22+
_data = new BehaviorSubject<T[]>([]);
23+
24+
/**
25+
* Data for the nested treee
26+
*/
27+
get data() { return this._data.value; }
28+
set data(value: T[]) { this._data.next(value); }
29+
30+
connect(collectionViewer: CollectionViewer): Observable<T[]> {
31+
return merge([collectionViewer.viewChange, this._data])
32+
.pipe(map(() => {
33+
return this.data;
34+
}));
35+
}
36+
37+
disconnect() {
38+
// no op
39+
}
40+
}
41+

src/material-experimental/tree/public-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ export * from './padding';
1212
export * from './tree';
1313
export * from './tree-module';
1414
export * from './toggle';
15+
export * from './data-source/flat-data-source';
16+
export * from './data-source/nested-data-source';

src/material-experimental/tree/tree-module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const MAT_TREE_DIRECTIVES = [
2424
MatTreeNodeToggle,
2525
MatTree,
2626
MatTreeNode,
27-
MatTreeNodeOutlet,
27+
MatTreeNodeOutlet
2828
];
2929

3030
@NgModule({

0 commit comments

Comments
 (0)