diff --git a/src/cdk/tree/nested-node.ts b/src/cdk/tree/nested-node.ts index 7768b95719d..aec4438ccdc 100644 --- a/src/cdk/tree/nested-node.ts +++ b/src/cdk/tree/nested-node.ts @@ -17,6 +17,7 @@ import {takeUntil} from 'rxjs/operators/takeUntil'; import {CdkTree, CdkTreeNode} from './tree'; import {CdkTreeNodeOutlet} from './outlet'; +import {getTreeControlFunctionsMissingError} from './tree-errors'; /** * Nested node is a child of ``. It works with nested tree. @@ -62,6 +63,9 @@ export class CdkNestedTreeNode extends CdkTreeNode implements AfterContent } ngAfterContentInit() { + if (!this._tree.treeControl.getChildren) { + throw getTreeControlFunctionsMissingError(); + } this._tree.treeControl.getChildren(this.data).pipe(takeUntil(this._destroyed)) .subscribe(result => { if (result && result.length) { diff --git a/src/cdk/tree/tree.spec.ts b/src/cdk/tree/tree.spec.ts index 6919c15bce1..d778b08d3ae 100644 --- a/src/cdk/tree/tree.spec.ts +++ b/src/cdk/tree/tree.spec.ts @@ -14,11 +14,13 @@ import {Observable} from 'rxjs/Observable'; import {combineLatest} from 'rxjs/observable/combineLatest'; import {map} from 'rxjs/operators/map'; +import {BaseTreeControl} from './control/base-tree-control'; import {TreeControl} from './control/tree-control'; import {FlatTreeControl} from './control/flat-tree-control'; import {NestedTreeControl} from './control/nested-tree-control'; import {CdkTreeModule} from './index'; import {CdkTree} from './tree'; +import {getTreeControlFunctionsMissingError} from './tree-errors'; describe('CdkTree', () => { @@ -587,6 +589,18 @@ describe('CdkTree', () => { [`[topping_3] - [cheese_3] + [base_3]`]); }); }); + + it('should throw an error when missing function in nested tree', () => { + configureCdkTreeTestingModule([NestedCdkErrorTreeApp]); + expect(() => TestBed.createComponent(NestedCdkErrorTreeApp).detectChanges()) + .toThrowError(getTreeControlFunctionsMissingError().message); + }); + + it('should throw an error when missing function in flat tree', () => { + configureCdkTreeTestingModule([FlatCdkErrorTreeApp]); + expect(() => TestBed.createComponent(FlatCdkErrorTreeApp).detectChanges()) + .toThrowError(getTreeControlFunctionsMissingError().message); + }); }); }); @@ -994,3 +1008,56 @@ class ObservableDataSourceNestedCdkTreeApp { @ViewChild(CdkTree) tree: CdkTree; } +@Component({ + template: ` + + + {{node.pizzaTopping}} - {{node.pizzaCheese}} + {{node.pizzaBase}} + + + + ` +}) +class NestedCdkErrorTreeApp { + getLevel = (node: TestData) => node.level; + + isExpandable = (node: TestData) => node.children.length > 0; + + treeControl: TreeControl = new FlatTreeControl(this.getLevel, this.isExpandable); + + dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl); + + @ViewChild(CdkTree) tree: CdkTree; +} + +class FakeTreeControl extends BaseTreeControl { + getDescendants(_: TestData): TestData[] { + return this.dataNodes; + } + + expandAll(): void { + // No op + } +} +@Component({ + template: ` + + + {{node.pizzaTopping}} - {{node.pizzaCheese}} + {{node.pizzaBase}} + + + + ` +}) +class FlatCdkErrorTreeApp { + + getLevel = (node: TestData) => node.level; + + isExpandable = (node: TestData) => node.children.length > 0; + + treeControl: TreeControl = new FakeTreeControl(); + + dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl); + + @ViewChild(CdkTree) tree: CdkTree; +} diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 3abf6b953ea..fa80ea85f81 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -60,7 +60,7 @@ export class CdkTreeNode implements FocusableOption, OnDestroy { * The most recently created `CdkTreeNode`. We save it in static variable so we can retrieve it * in `CdkTree` and set the data to it. */ - static mostRecentTreeNode: CdkTreeNode | null = null; + static mostRecentTreeNode: CdkTreeNode<{}> | null = null; /** Subject that emits when the component has been destroyed. */ protected _destroyed = new Subject(); @@ -89,7 +89,7 @@ export class CdkTreeNode implements FocusableOption, OnDestroy { constructor(protected _elementRef: ElementRef, protected _tree: CdkTree) { - CdkTreeNode.mostRecentTreeNode = this; + CdkTreeNode.mostRecentTreeNode = this as CdkTreeNode; } ngOnDestroy() { @@ -140,7 +140,7 @@ export class CdkTree implements CollectionViewer, OnInit, OnDestroy { private _onDestroy = new Subject(); /** Latest data provided by the data source through the connect interface. */ - private _data: Array = new Array(); + private _data = new Array(); /** Differ used to find the changes in the data provided by the data source. */ private _dataDiffer: IterableDiffer; @@ -304,7 +304,7 @@ export class CdkTree implements CollectionViewer, OnInit, OnDestroy { * definition. */ _getNodeDef(data: T, i: number): CdkTreeNodeDef { - if (this._nodeDefs.length == 1) { return this._nodeDefs.first; } + if (this._nodeDefs.length === 1) { return this._nodeDefs.first; } const nodeDef = this._nodeDefs.find(def => def.when && def.when(i, data)) || this._defaultNodeDef; @@ -321,7 +321,7 @@ export class CdkTree implements CollectionViewer, OnInit, OnDestroy { const node = this._getNodeDef(nodeData, index); // Node context that will be provided to created embedded view - const context: CdkTreeNodeOutletContext = new CdkTreeNodeOutletContext(nodeData); + const context = new CdkTreeNodeOutletContext(nodeData); // Use default tree nodeOutlet, or nested node's nodeOutlet const container = viewContainer ? viewContainer : this._nodeOutlet.viewContainer; diff --git a/src/demo-app/tree/checklist-tree-demo/checklist-tree-demo.ts b/src/demo-app/tree/checklist-tree-demo/checklist-tree-demo.ts index 7484ed9c953..414f6f0c899 100644 --- a/src/demo-app/tree/checklist-tree-demo/checklist-tree-demo.ts +++ b/src/demo-app/tree/checklist-tree-demo/checklist-tree-demo.ts @@ -11,6 +11,7 @@ import {SelectionModel} from '@angular/cdk/collections'; import {FlatTreeControl} from '@angular/cdk/tree'; import {MatTreeFlattener, MatTreeFlatDataSource} from '@angular/material/tree'; import {of as ofObservable} from 'rxjs/observable/of'; +import {Observable} from 'rxjs/Observable'; import {TodoItemNode, TodoItemFlatNode, ChecklistDatabase} from './checklist-database'; /** @@ -61,7 +62,9 @@ export class ChecklistTreeDemo { isExpandable = (node: TodoItemFlatNode) => { return node.expandable; }; - getChildren = (node: TodoItemNode) => { return ofObservable(node.children); }; + getChildren = (node: TodoItemNode): Observable => { + return ofObservable(node.children); + } hasChild = (_: number, _nodeData: TodoItemFlatNode) => { return _nodeData.expandable; }; diff --git a/src/demo-app/tree/dynamic-tree-demo/dynamic-database.ts b/src/demo-app/tree/dynamic-tree-demo/dynamic-database.ts index 7c1a25ca750..ab0c8320291 100644 --- a/src/demo-app/tree/dynamic-tree-demo/dynamic-database.ts +++ b/src/demo-app/tree/dynamic-tree-demo/dynamic-database.ts @@ -9,6 +9,7 @@ import {Injectable} from '@angular/core'; import {FlatTreeControl} from '@angular/cdk/tree'; import {CollectionViewer, SelectionChange} from '@angular/cdk/collections'; import {BehaviorSubject} from 'rxjs/BehaviorSubject'; +import {Observable} from 'rxjs/Observable'; import {merge} from 'rxjs/observable/merge'; import {map} from 'rxjs/operators/map'; @@ -67,12 +68,9 @@ export class DynamicDataSource { constructor(private treeControl: FlatTreeControl, private database: DynamicDatabase) {} - connect(collectionViewer: CollectionViewer) { - const changes = [ - collectionViewer.viewChange, - this.treeControl.expansionModel.onChange!, - ]; - return merge(...changes).pipe(map((change) => { + connect(collectionViewer: CollectionViewer): Observable { + return merge(collectionViewer.viewChange, this.treeControl.expansionModel.onChange!) + .pipe(map((change) => { if ((change as SelectionChange).added || (change as SelectionChange).removed) { this.handleTreeControl(change as SelectionChange); diff --git a/src/demo-app/tree/loadmore-tree-demo/loadmore-tree-demo.ts b/src/demo-app/tree/loadmore-tree-demo/loadmore-tree-demo.ts index 35cf81da36b..cfb69eac996 100644 --- a/src/demo-app/tree/loadmore-tree-demo/loadmore-tree-demo.ts +++ b/src/demo-app/tree/loadmore-tree-demo/loadmore-tree-demo.ts @@ -9,6 +9,7 @@ import {Component} from '@angular/core'; import {FlatTreeControl} from '@angular/cdk/tree'; import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree'; +import {Observable} from 'rxjs/Observable'; import {LoadmoreNode, LoadmoreFlatNode, LoadmoreDatabase} from './loadmore-database'; const LOAD_MORE = 'LOAD_MORE'; @@ -50,7 +51,7 @@ export class LoadmoreTreeDemo { database.initialize(); } - getChildren = (node: LoadmoreNode) => { return node.childrenChange; }; + getChildren = (node: LoadmoreNode): Observable => { return node.childrenChange; }; transformer = (node: LoadmoreNode, level: number) => { if (this.nodeMap.has(node.item)) {