import {Component, Injectable} from '@angular/core'; import {FlatTreeControl} from '@angular/cdk/tree'; import {MatTreeFlattener, MatTreeFlatDataSource} from '@angular/material/tree'; import {of, Observable, BehaviorSubject} from 'rxjs'; /** * File node data with nested structure. * Each node has a filename, and a type or a list of children. */ export class FileNode { children: FileNode[]; filename: string; type: any; } /** Flat node with expandable and level information */ export class FileFlatNode { filename: string; type: any; level: number; expandable: boolean; } /** * The file structure tree data in string. The data could be parsed into a Json object */ const TREE_DATA = ` { "Documents": { "angular": { "src": { "core": "ts", "compiler": "ts" } }, "material2": { "src": { "button": "ts", "checkbox": "ts", "input": "ts" } } }, "Downloads": { "Tutorial": "html", "November": "pdf", "October": "pdf" }, "Pictures": { "Sun": "png", "Woods": "jpg", "Photo Booth Library": { "Contents": "dir", "Pictures": "dir" } }, "Applications": { "Chrome": "app", "Calendar": "app", "Webstorm": "app" } }`; /** * File database, it can build a tree structured Json object from string. * Each node in Json object represents a file or a directory. For a file, it has filename and type. * For a directory, it has filename and children (a list of files or directories). * The input will be a json object string, and the output is a list of `FileNode` with nested * structure. */ @Injectable() export class FileDatabase { dataChange: BehaviorSubject = new BehaviorSubject([]); get data(): FileNode[] { return this.dataChange.value; } constructor() { this.initialize(); } initialize() { // Parse the string to json object. const dataObject = JSON.parse(TREE_DATA); // Build the tree nodes from Json object. The result is a list of `FileNode` with nested // file node as children. const data = this.buildFileTree(dataObject, 0); // Notify the change. this.dataChange.next(data); } /** * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. * The return value is the list of `FileNode`. */ buildFileTree(value: any, level: number): FileNode[] { let data: any[] = []; for (let k in value) { let v = value[k]; let node = new FileNode(); node.filename = `${k}`; if (v === null || v === undefined) { // no action } else if (typeof v === 'object') { node.children = this.buildFileTree(v, level + 1); } else { node.type = v; } data.push(node); } return data; } } /** * @title Tree with flat nodes */ @Component({ selector: 'cdk-tree-flat-example', templateUrl: 'cdk-tree-flat-example.html', styleUrls: ['cdk-tree-flat-example.css'], providers: [FileDatabase] }) export class CdkTreeFlatExample { treeControl: FlatTreeControl; treeFlattener: MatTreeFlattener; dataSource: MatTreeFlatDataSource; constructor(database: FileDatabase) { this.treeFlattener = new MatTreeFlattener(this.transformer, this._getLevel, this._isExpandable, this._getChildren); this.treeControl = new FlatTreeControl(this._getLevel, this._isExpandable); this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); database.dataChange.subscribe(data => { this.dataSource.data = data; }); } transformer = (node: FileNode, level: number) => { let flatNode = new FileFlatNode(); flatNode.filename = node.filename; flatNode.type = node.type; flatNode.level = level; flatNode.expandable = !!node.children; return flatNode; } private _getLevel = (node: FileFlatNode) => { return node.level; }; private _isExpandable = (node: FileFlatNode) => { return node.expandable; }; private _getChildren = (node: FileNode): Observable => { return of(node.children); }; hasChild = (_: number, _nodeData: FileFlatNode) => { return _nodeData.expandable; }; }