Skip to content

Commit 2a9a03f

Browse files
vivian-hu-zzjosephperrott
authored andcommitted
fix(tree): allow tree node to have undefined child or null child (angular#14482)
1 parent 727d6d1 commit 2a9a03f

File tree

2 files changed

+110
-7
lines changed

2 files changed

+110
-7
lines changed

src/lib/tree/data-source/flat-data-source.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export class MatTreeFlattener<T, F> {
5050
constructor(public transformFunction: (node: T, level: number) => F,
5151
public getLevel: (node: F) => number,
5252
public isExpandable: (node: F) => boolean,
53-
public getChildren: (node: T) => Observable<T[]> | T[]) {}
53+
public getChildren: (node: T) =>
54+
Observable<T[]> | T[] | undefined | null) {}
5455

5556
_flattenNode(node: T, level: number,
5657
resultNodes: F[], parentMap: boolean[]): F[] {
@@ -59,12 +60,14 @@ export class MatTreeFlattener<T, F> {
5960

6061
if (this.isExpandable(flatNode)) {
6162
const childrenNodes = this.getChildren(node);
62-
if (Array.isArray(childrenNodes)) {
63-
this._flattenChildren(childrenNodes, level, resultNodes, parentMap);
64-
} else {
65-
childrenNodes.pipe(take(1)).subscribe(children => {
66-
this._flattenChildren(children, level, resultNodes, parentMap);
67-
});
63+
if (childrenNodes) {
64+
if (Array.isArray(childrenNodes)) {
65+
this._flattenChildren(childrenNodes, level, resultNodes, parentMap);
66+
} else {
67+
childrenNodes.pipe(take(1)).subscribe(children => {
68+
this._flattenChildren(children, level, resultNodes, parentMap);
69+
});
70+
}
6871
}
6972
}
7073
return resultNodes;

src/lib/tree/tree.spec.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,27 @@ describe('MatTree', () => {
216216
});
217217
});
218218

219+
describe('flat tree with undefined or null children', () => {
220+
describe('should initialize', () => {
221+
let fixture: ComponentFixture<MatTreeWithNullOrUndefinedChild >;
222+
223+
beforeEach(() => {
224+
configureMatTreeTestingModule([MatTreeWithNullOrUndefinedChild ]);
225+
fixture = TestBed.createComponent(MatTreeWithNullOrUndefinedChild );
226+
treeElement = fixture.nativeElement.querySelector('mat-tree');
227+
228+
fixture.detectChanges();
229+
});
230+
231+
it('with rendered dataNodes', () => {
232+
const nodes = getNodes(treeElement);
233+
234+
expect(nodes).toBeDefined('Expect nodes to be defined');
235+
expect(nodes[0].classList).toContain('customNodeClass');
236+
});
237+
});
238+
});
239+
219240
describe('nested tree', () => {
220241
describe('should initialize', () => {
221242
let fixture: ComponentFixture<NestedMatTreeApp>;
@@ -610,6 +631,85 @@ class SimpleMatTreeApp {
610631
}
611632
}
612633

634+
interface FoodNode {
635+
name: string;
636+
children?: FoodNode[] | null;
637+
}
638+
639+
/** Flat node with expandable and level information */
640+
interface ExampleFlatNode {
641+
expandable: boolean;
642+
name: string;
643+
level: number;
644+
}
645+
646+
/**
647+
* Food data with nested structure.
648+
* Each node has a name and an optiona list of children.
649+
*/
650+
const TREE_DATA: FoodNode[] = [
651+
{
652+
name: 'Fruit',
653+
children: [
654+
{name: 'Apple'},
655+
{name: 'Banana'},
656+
{name: 'Fruit loops',
657+
children: null},
658+
]
659+
}, {
660+
name: 'Vegetables',
661+
children: [
662+
{
663+
name: 'Green',
664+
children: [
665+
{name: 'Broccoli'},
666+
{name: 'Brussel sprouts'},
667+
]
668+
}, {
669+
name: 'Orange',
670+
children: [
671+
{name: 'Pumpkins'},
672+
{name: 'Carrots'},
673+
]
674+
},
675+
]
676+
},
677+
];
678+
679+
@Component({
680+
template: `
681+
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
682+
<mat-tree-node *matTreeNodeDef="let node" class="customNodeClass"
683+
matTreeNodePadding matTreeNodeToggle>
684+
{{node.name}}
685+
</mat-tree-node>
686+
</mat-tree>
687+
`
688+
})
689+
class MatTreeWithNullOrUndefinedChild {
690+
private transformer = (node: FoodNode, level: number) => {
691+
return {
692+
expandable: !!node.children,
693+
name: node.name,
694+
level: level,
695+
};
696+
}
697+
698+
treeControl = new FlatTreeControl<ExampleFlatNode>(
699+
node => node.level, node => node.expandable);
700+
701+
treeFlattener = new MatTreeFlattener(
702+
this.transformer, node => node.level, node => node.expandable, node => node.children);
703+
704+
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener, TREE_DATA);
705+
706+
constructor() {
707+
this.dataSource.data = TREE_DATA;
708+
}
709+
710+
hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
711+
}
712+
613713
@Component({
614714
template: `
615715
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">

0 commit comments

Comments
 (0)