Sometimes it can happen that the data comes to us instead in the form of a tree, with objects that contain lists of other objects, which in turn contain lists of other objects.
In this case, we can think of starting with the recursion on the data, to modify them in the linear form. To do this there are various ways. The most versatile is to use an Angular pipe that scrolls the object and its children, a recursion pipe.
Imagine you have a json object like this:
{
question: 'Need',
nodes: [
{
qid: 1,
question: 'what',
answer: 'Hardware',
nodes: [
{
qid: 3,
answer: 'Printer',
question: 'for',
nodes: [
{
qid: 10,
answer: 'Toner',
nodes: []
}, {
qid: 11,
answer: 'Broken',
nodes: []
}
]
},
{
qid: 6,
answer: 'PC',
question: 'for',
nodes: [
{
qid: 7,
answer: 'Charger',
question: 'and',
nodes: [
{
qid: 8,
answer: 'Corrupted charger',
nodes: []
}
]
}
]
}
]
},
{
qid: 2,
question: 'what',
answer: 'Software',
nodes: [
{
qid: 4,
answer: 'MsOffice',
nodes: []
}, {
qid: 5,
answer: 'Photoshop',
nodes: []
}
]
}
]
}
First of all we proceed to create our pipe, which must have a “transform” method inside.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'recursive'
})
export class RecursivePipe implements PipeTransform {
/**
* Recursive pipe method.
*/
transform(nodes: Iterable<{}>): any[] {
return nodes;
}
}
In the transform method we are going to call another method, private. It will recursion on itself to scroll the whole tree, from the container object, gradually on all branches.
To make our pipe as generic as possible (and therefore more flexible), we will give the possibility to insert some properties to understand how the tree, on which we will cycle, is structured.
parent
Property to use like parent property of nodes. The first cicle has parent set to null (it is the node without parent, the root node).childrenProp
Property of children inside one node.list
List of property to search inside every node. For every node we search if there is an array of expected properties.
To our recursive method we pass the list of nodes to work on and the parent as null
.
export class RecursivePipe implements PipeTransform {
private foundItems = []; // What returns this pipe.
private prop: string;
private list: string[];
private childrenPropName: string;
/**
* Recursive filtered pipe method.
* @param nodes "Tree nodes"
* @param parent "prop to use like parent property"
* @param childrenProp "Children parent for recursion"
* @param list "Props to get"
*/
transform(
nodes: Iterable<{}>,
parent?: string,
childrenProp?: string,
list?: string[]
): any {
this.foundItems = [];
this.parent = parent;
this.list = list;
this.childrenPropName = childrenProp;
this.searchRecursive(nodes, null); // recursion method
return this.foundItems;
}
...searchRecursive...
}
Now we can create our core method, with recursion.
This method will loop all our tree, looking up to the leaf nodes for the required properties, retrieving them from each node.
// Recursion on it.
private searchRecursive(nodes, parent: string) {
if (nodes) {
// Loop on children nodes.
nodes.forEach(n => {
const nodeProp = String(n[this.parent]);
// If props to list are arrays, add its items to a flat array.
if (this.list.length < 2 && typeof n[this.list[0]] !== 'string') {
this.foundItems = [...this.foundItems, ...n[this.list[0]]];
} else {
// If props to list are strings, create a new object, then push it to a flat array.
const tmp = { parent };
this.list.forEach(e => {
if (typeof n[e] !== 'undefined') {
tmp[e] = String(n[e]);
}
});
this.foundItems.push(tmp);
}
this.searchRecursive(n[this.childrenPropName], nodeProp);
});
}
}
To use our method we go to the template where we will use the object tree and add a pipe (character |
) followed by the identifier inserted in the decorator (in our case, name: 'recursive'
).
For our pipe we need the parameters that we had inserted into the method transform()
: parent, childrenProp and list.
<select>
<option value="" selected="" disabled="disabled">search all...</option>
<option *ngFor="let select of (this.treeQuestions.nodes | recursive
: 'qid'
: 'nodes'
: ['answer', 'question', 'qid'])" [value]="select.qid">{{
select.answer
}}</option>
</select>
Below is an example of what we have seen about the recursion pipe: