diff --git a/src/lib/tree/data-source/flat-data-source.ts b/src/lib/tree/data-source/flat-data-source.ts index 03463b437cc..fce72e2cb63 100644 --- a/src/lib/tree/data-source/flat-data-source.ts +++ b/src/lib/tree/data-source/flat-data-source.ts @@ -50,7 +50,8 @@ export class MatTreeFlattener { constructor(public transformFunction: (node: T, level: number) => F, public getLevel: (node: F) => number, public isExpandable: (node: F) => boolean, - public getChildren: (node: T) => Observable | T[]) {} + public getChildren: (node: T) => + Observable | T[] | undefined | null) {} _flattenNode(node: T, level: number, resultNodes: F[], parentMap: boolean[]): F[] { @@ -59,12 +60,14 @@ export class MatTreeFlattener { if (this.isExpandable(flatNode)) { const childrenNodes = this.getChildren(node); - if (Array.isArray(childrenNodes)) { - this._flattenChildren(childrenNodes, level, resultNodes, parentMap); - } else { - childrenNodes.pipe(take(1)).subscribe(children => { - this._flattenChildren(children, level, resultNodes, parentMap); - }); + if (childrenNodes) { + if (Array.isArray(childrenNodes)) { + this._flattenChildren(childrenNodes, level, resultNodes, parentMap); + } else { + childrenNodes.pipe(take(1)).subscribe(children => { + this._flattenChildren(children, level, resultNodes, parentMap); + }); + } } } return resultNodes; diff --git a/src/lib/tree/tree.spec.ts b/src/lib/tree/tree.spec.ts index e9c1e541d0c..098b2ae0471 100644 --- a/src/lib/tree/tree.spec.ts +++ b/src/lib/tree/tree.spec.ts @@ -216,6 +216,27 @@ describe('MatTree', () => { }); }); + describe('flat tree with undefined or null children', () => { + describe('should initialize', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + configureMatTreeTestingModule([MatTreeWithNullOrUndefinedChild ]); + fixture = TestBed.createComponent(MatTreeWithNullOrUndefinedChild ); + treeElement = fixture.nativeElement.querySelector('mat-tree'); + + fixture.detectChanges(); + }); + + it('with rendered dataNodes', () => { + const nodes = getNodes(treeElement); + + expect(nodes).toBeDefined('Expect nodes to be defined'); + expect(nodes[0].classList).toContain('customNodeClass'); + }); + }); + }); + describe('nested tree', () => { describe('should initialize', () => { let fixture: ComponentFixture; @@ -610,6 +631,85 @@ class SimpleMatTreeApp { } } +interface FoodNode { + name: string; + children?: FoodNode[] | null; +} + +/** Flat node with expandable and level information */ +interface ExampleFlatNode { + expandable: boolean; + name: string; + level: number; +} + +/** + * Food data with nested structure. + * Each node has a name and an optiona list of children. + */ +const TREE_DATA: FoodNode[] = [ + { + name: 'Fruit', + children: [ + {name: 'Apple'}, + {name: 'Banana'}, + {name: 'Fruit loops', + children: null}, + ] + }, { + name: 'Vegetables', + children: [ + { + name: 'Green', + children: [ + {name: 'Broccoli'}, + {name: 'Brussel sprouts'}, + ] + }, { + name: 'Orange', + children: [ + {name: 'Pumpkins'}, + {name: 'Carrots'}, + ] + }, + ] + }, +]; + +@Component({ + template: ` + + + {{node.name}} + + + ` +}) +class MatTreeWithNullOrUndefinedChild { + private transformer = (node: FoodNode, level: number) => { + return { + expandable: !!node.children, + name: node.name, + level: level, + }; + } + + treeControl = new FlatTreeControl( + node => node.level, node => node.expandable); + + treeFlattener = new MatTreeFlattener( + this.transformer, node => node.level, node => node.expandable, node => node.children); + + dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener, TREE_DATA); + + constructor() { + this.dataSource.data = TREE_DATA; + } + + hasChild = (_: number, node: ExampleFlatNode) => node.expandable; +} + @Component({ template: `