/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {FlatTreeControl} from '@angular/cdk/tree'; import {Component, Injectable} from '@angular/core'; import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree'; import {BehaviorSubject, Observable} from 'rxjs'; const LOAD_MORE = 'LOAD_MORE'; /** Nested node */ export class LoadmoreNode { childrenChange = new BehaviorSubject([]); get children(): LoadmoreNode[] { return this.childrenChange.value; } constructor(public item: string, public hasChildren = false, public loadMoreParentItem: string | null = null) {} } /** Flat node with expandable and level information */ export class LoadmoreFlatNode { constructor(public item: string, public level = 1, public expandable = false, public loadMoreParentItem: string | null = null) {} } /** * A database that only load part of the data initially. After user clicks on the `Load more` * button, more data will be loaded. */ @Injectable() export class LoadmoreDatabase { batchNumber = 5; dataChange = new BehaviorSubject([]); nodeMap = new Map(); /** The data */ rootLevelNodes: string[] = ['Vegetables', 'Fruits']; dataMap = new Map([ ['Fruits', ['Apple', 'Orange', 'Banana']], ['Vegetables', ['Tomato', 'Potato', 'Onion']], ['Apple', ['Fuji', 'Macintosh']], ['Onion', ['Yellow', 'White', 'Purple', 'Green', 'Shallot', 'Sweet', 'Red', 'Leek']], ]); initialize() { const data = this.rootLevelNodes.map(name => this._generateNode(name)); this.dataChange.next(data); } /** Expand a node whose children are not loaded */ loadMore(item: string, onlyFirstTime = false) { if (!this.nodeMap.has(item) || !this.dataMap.has(item)) { return; } const parent = this.nodeMap.get(item)!; const children = this.dataMap.get(item)!; if (onlyFirstTime && parent.children!.length > 0) { return; } const newChildrenNumber = parent.children!.length + this.batchNumber; const nodes = children.slice(0, newChildrenNumber) .map(name => this._generateNode(name)); if (newChildrenNumber < children.length) { // Need a new load more node nodes.push(new LoadmoreNode(LOAD_MORE, false, item)); } parent.childrenChange.next(nodes); this.dataChange.next(this.dataChange.value); } private _generateNode(item: string): LoadmoreNode { if (this.nodeMap.has(item)) { return this.nodeMap.get(item)!; } const result = new LoadmoreNode(item, this.dataMap.has(item)); this.nodeMap.set(item, result); return result; } } /** * @title Tree with partially loaded data */ @Component({ selector: 'tree-loadmore-example', templateUrl: 'tree-loadmore-example.html', styleUrls: ['tree-loadmore-example.css'], providers: [LoadmoreDatabase] }) export class TreeLoadmoreExample { nodeMap = new Map(); treeControl: FlatTreeControl; treeFlattener: MatTreeFlattener; // Flat tree data source dataSource: MatTreeFlatDataSource; constructor(private _database: LoadmoreDatabase) { 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; }); _database.initialize(); } getChildren = (node: LoadmoreNode): Observable => node.childrenChange; transformer = (node: LoadmoreNode, level: number) => { const existingNode = this.nodeMap.get(node.item); if (existingNode) { return existingNode; } const newNode = new LoadmoreFlatNode(node.item, level, node.hasChildren, node.loadMoreParentItem); this.nodeMap.set(node.item, newNode); return newNode; } getLevel = (node: LoadmoreFlatNode) => node.level; isExpandable = (node: LoadmoreFlatNode) => node.expandable; hasChild = (_: number, _nodeData: LoadmoreFlatNode) => _nodeData.expandable; isLoadMore = (_: number, _nodeData: LoadmoreFlatNode) => _nodeData.item === LOAD_MORE; /** Load more nodes from data source */ loadMore(item: string) { this._database.loadMore(item); } loadChildren(node: LoadmoreFlatNode) { this._database.loadMore(node.item, true); } }