Skip to content

Instantly share code, notes, and snippets.

@thapld
Created October 23, 2018 10:37
Show Gist options
  • Save thapld/c18537de07037e759d612a240754c568 to your computer and use it in GitHub Desktop.
Save thapld/c18537de07037e759d612a240754c568 to your computer and use it in GitHub Desktop.
import {ResourceService} from '../../services/resource.service';
import {FlatTreeControl} from '@angular/cdk/tree';
import {ResourceNode} from '../resource-node';
import {BehaviorSubject, merge, Observable} from 'rxjs';
import {CollectionViewer, SelectionChange} from '@angular/cdk/collections';
import {map} from 'rxjs/operators';
import {ResourceTree} from '../resource-tree';
export class ResourcesTreeDatasource {
dataChange: BehaviorSubject<ResourceNode[]> = new BehaviorSubject<ResourceNode[]>([]);
constructor(private resourceService: ResourceService,
private treeControl: FlatTreeControl<ResourceNode>) {
}
initialData() {
this.resourceService.findAllParent().subscribe(
value => {
const rns: ResourceNode[] = [];
const rm: ResourceTree[] = value;
for (let i = 0; i < rm.length; i++) {
const rn = new ResourceNode();
rn.title = rm[i].title;
rn.status = rm[i].status;
rn.type = rm[i].type;
rn.id = rm[i].id;
rn.level = 0;
rn.expandable = rm[i].childrenCount > 0;
rns.push(rn);
}
this.data = rns;
},
error => {
console.log(error);
}
);
}
get data(): ResourceNode[] {
return this.dataChange.value;
}
set data(value: ResourceNode[]) {
this.treeControl.dataNodes = value;
this.dataChange.next(value);
}
connect(collectionViewer: CollectionViewer): Observable<ResourceNode[]> {
this.treeControl.expansionModel.changed.subscribe(change => {
if ((change as SelectionChange<ResourceNode>).added ||
(change as SelectionChange<ResourceNode>).removed) {
this.handleTreeControl(change as SelectionChange<ResourceNode>);
}
});
return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
}
/** Handle expand/collapse behaviors */
handleTreeControl(change: SelectionChange<ResourceNode>) {
if (change.added) {
change.added.forEach((node) => this.toggleNode(node, true));
}
if (change.removed) {
change.removed.reverse().forEach((node) => this.toggleNode(node, false));
}
}
/**
* Toggle the node, remove from display list
*/
toggleNode(node: ResourceNode, expand: boolean) {
node.isLoading = true;
const index = this.data.indexOf(node);
if (index < 0) {
node.isLoading = false;
return;
}
if (expand) {
//Avoid call db
if(node.item && node.item.length > 0) {
this.data.splice(index + 1, 0, ...node.item);
node.isLoading = false;
this.dataChange.next(this.data);
}else {
this.resourceService.findAllChildByParent(node.id)
.pipe(
map(value => {
return value.map(rm => {
const rn = new ResourceNode();
rn.title = rm.title;
rn.status = rm.status;
rn.type = rm.type;
rn.level = node.level + 1;
rn.id = rm.id;
rn.expandable = rm.childrenCount > 0;
return rn;
});
})
)
.subscribe(
children => {
if (!children) {
return;
}
node.item = children;
this.data.splice(index + 1, 0, ...children);
node.isLoading = false;
this.dataChange.next(this.data);
}, error => {
console.log(error);
node.isLoading = false;
this.dataChange.next(this.data);
}
);
}
} else {
let count = 0;
for (let i = index + 1; i < this.data.length; i++) {
if (this.data[i].level > node.level) {
count++;
}
}
this.data.splice(index + 1, count);
node.isLoading = false;
this.dataChange.next(this.data);
}
}
}
<h3>Tree Resource component</h3>
<div>
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
<button mat-icon-button disabled></button>
<mat-checkbox class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
(change)="checklistSelection.toggle(node)">{{node.title}}
</mat-checkbox>
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.title">
<mat-icon class="mat-icon-rtl-mirror">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
<mat-checkbox
[checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="resourceItemSelectionToggle(node)">{{node.title}}
</mat-checkbox>
<mat-progress-bar *ngIf="node.isLoading"
mode="indeterminate"
class="tree-progress-bar">
</mat-progress-bar>
</mat-tree-node>
</mat-tree>
<pre>
{{checklistSelection.selected | json}}
</pre>
import {Component, OnInit} from '@angular/core';
import {ResourceService} from '../../_core/services/resource.service';
import {FlatTreeControl} from '@angular/cdk/tree';
import {ResourceNode} from '../../_core/models/resource-node';
import {ResourcesTreeDatasource} from '../../_core/models/data-sources/resources-tree.datasource';
import {SelectionModel} from '@angular/cdk/collections';
@Component({
selector: 'm-test-component',
templateUrl: './test-component.component.html',
styleUrls: ['./test-component.component.scss'],
})
export class TestComponentComponent implements OnInit {
treeControl: FlatTreeControl<ResourceNode>;
dataSource: ResourcesTreeDatasource;
checklistSelection = new SelectionModel<ResourceNode>(false);
constructor(private resourceService: ResourceService) {
this.treeControl = new FlatTreeControl<ResourceNode>(this.getLevel, this.isExpandable);
this.dataSource = new ResourcesTreeDatasource(this.resourceService, this.treeControl);
this.dataSource.initialData();
}
ngOnInit(): void {
}
getLevel = (node: ResourceNode) => {
return node.level;
};
isExpandable = (node: ResourceNode) => {
return node.expandable;
};
hasChild = (_: number, _nodeData: ResourceNode) => {
return _nodeData.expandable;
};
/** Whether all the descendants of the node are selected */
descendantsAllSelected(node: ResourceNode): boolean {
const descendants = this.treeControl.getDescendants(node);
return descendants.every(child => {
return this.checklistSelection.isSelected(child);
});
}
/** Whether part of the descendants are selected */
descendantsPartiallySelected(node: ResourceNode): boolean {
const descendants = this.treeControl.getDescendants(node);
const result = descendants.some(child => {
return this.checklistSelection.isSelected(child)
});
return result && !this.descendantsAllSelected(node);
}
/** Toggle the to-do item selection. Select/deselect all the descendants node */
resourceItemSelectionToggle(node: ResourceNode): void {
this.checklistSelection.toggle(node);
const descendants = this.treeControl.getDescendants(node);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment