Skip to content

Commit 202b97f

Browse files
committed
feat(tree): add data sources for tree
1 parent 8116194 commit 202b97f

File tree

7 files changed

+944
-123
lines changed

7 files changed

+944
-123
lines changed

src/cdk/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/tree';
11+
import {MatTreeFlattener, MatTreeFlatDataSource, MatTreeNestedDataSource} from '@angular/material/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/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+
*
20+
* For example, the input data is nested:
21+
* SomeNode: {
22+
* key: 'Fruits',
23+
* children: [
24+
* NodeOne: {
25+
* key: 'Apple',
26+
* },
27+
* NodeTwo: {
28+
* key: 'Pear',
29+
* }
30+
* ]
31+
* }
32+
* After flattener flatten the tree, the structure will become
33+
* SomeNode: {
34+
* key: 'Fruits',
35+
* expandable: true,
36+
* level: 1
37+
* },
38+
* NodeOne: {
39+
* key: 'Apple',
40+
* expandable: false,
41+
* level: 2
42+
* },
43+
* NodeTwo: {
44+
* key: 'Pear',
45+
* expandable: false,
46+
* level: 2
47+
* }
48+
*/
49+
export class MatTreeFlattener<T, F> {
50+
51+
constructor(public transformFunction: (node: T, level: number) => F,
52+
public getLevel: (node: F) => number,
53+
public isExpandable: (node: F) => boolean,
54+
public getChildren: (node: T) => Observable<T[]>) {}
55+
56+
_flattenNode(node: T, level: number,
57+
resultNodes: F[], parentMap: boolean[]): F[] {
58+
const flatNode = this.transformFunction(node, level);
59+
resultNodes.push(flatNode);
60+
61+
if (this.isExpandable(flatNode)) {
62+
this.getChildren(node).pipe(take(1)).subscribe(children => {
63+
children.forEach((child, index) => {
64+
let childParentMap: boolean[] = parentMap.slice();
65+
childParentMap.push(index != children.length - 1);
66+
this._flattenNode(child, level + 1, resultNodes, childParentMap);
67+
});
68+
});
69+
}
70+
return resultNodes;
71+
}
72+
73+
/**
74+
* Flatten a list of node type T to flattened version of node F.
75+
* Please note that type T may be nested, and the length of `structuredData` may be different
76+
* from that of returned list `F[]`.
77+
*/
78+
flattenNodes(structuredData: T[]): F[] {
79+
let resultNodes: F[] = [];
80+
structuredData.forEach(node => this._flattenNode(node, 0, resultNodes, []));
81+
return resultNodes;
82+
}
83+
84+
/**
85+
* Expand flattened node with current expansion status.
86+
* The returned list may have different length.
87+
*/
88+
expandFlattenedNodes(nodes: F[], treeControl: TreeControl<F>): F[] {
89+
let results: F[] = [];
90+
let currentExpand: boolean[] = [];
91+
currentExpand[0] = true;
92+
93+
nodes.forEach((node) => {
94+
let expand = true;
95+
for (let i = 0; i <= this.getLevel(node); i++) {
96+
expand = expand && currentExpand[i];
97+
}
98+
if (expand) {
99+
results.push(node);
100+
}
101+
if (this.isExpandable(node)) {
102+
currentExpand[this.getLevel(node) + 1] = treeControl.isExpanded(node);
103+
}
104+
});
105+
return results;
106+
}
107+
}
108+
109+
110+
/**
111+
* Data source for flat tree.
112+
* The data source need to handle expansion/collapsion of the tree node and change the data feed
113+
* to `MatTree`.
114+
* The nested tree nodes of type `T` are flattened through `MatTreeFlattener`, and converted
115+
* to type `F` for `MatTree` to consume.
116+
*/
117+
export class MatTreeFlatDataSource<T, F> implements DataSource<F> {
118+
_flattenedData = new BehaviorSubject<F[]>([]);
119+
get flattenedData() { return this._flattenedData.value; }
120+
121+
_expandedData = new BehaviorSubject<F[]>([]);
122+
get expandedData() { return this._expandedData.value; }
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;
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, this.treeControl));
146+
return this.expandedData;
147+
}));
148+
}
149+
150+
disconnect() {
151+
}
152+
}
153+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
_renderedData = new BehaviorSubject<T[]>([]);
23+
get renderedData(): T[] { return this._renderedData.value; }
24+
25+
_data = new BehaviorSubject<T[]>([]);
26+
27+
/**
28+
* Data for the nested treee
29+
*/
30+
get data() { return this._data.value; }
31+
set data(value: T[]) { this._data.next(value); }
32+
33+
connect(collectionViewer: CollectionViewer): Observable<T[]> {
34+
return merge([collectionViewer.viewChange, this._data])
35+
.pipe(map(() => {
36+
this._renderedData.next(this.data);
37+
return this.renderedData;
38+
}));
39+
}
40+
41+
disconnect() {
42+
// no op
43+
}
44+
}
45+

src/lib/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 './trigger';
15+
export * from './data-source/flat-data-source';
16+
export * from './data-source/nested-data-source';

src/lib/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
MatTreeNodeTrigger,
2525
MatTree,
2626
MatTreeNode,
27-
MatTreeNodeOutlet,
27+
MatTreeNodeOutlet
2828
];
2929

3030
@NgModule({

0 commit comments

Comments
 (0)