FerdTurgusen
FerdTurgusen

Reputation: 350

How to perform a nested reduce operation with RxJs?

I'm trying to morph a series of file paths into a nested recursive structure for an Angular Material Tree component. The structure needs to be this:

interface FileTreeNode {
    name: string;
    children?: FileTreeNode[];
}

So a list of file paths such as ['path/to/file/01.txt', 'path/to/file/02.txt', 'another/to/file/03.txt'] will transform into:

[
    {name: 'path', children: [
        {name: 'to', children: [
            {name: 'file', children: [
                {name: '01.txt'}, {name: '02.txt'}
                ]
            }]
        }]
    },
    {name: 'another', children: [
        {name: 'to', children: [
            {name: 'file', children: [
                {name: '03.txt'}
                ]
            }]
        }]
    }
]

I saw a great answer for this problem here, which uses the built-in Array.reduce() method, but I'd like to accomplish this with RxJs operators.

Here is the code I'm trying to replicate:

var paths = ["path/to/file1.doc", "path/to/file2.doc", "foo/bar.doc"],
    result = [];
    
paths.reduce((r, path) => {
    path.split('/').reduce((o, name) => {
        var temp = (o.children = o.children || []).find(q => q.name === name);
        if (!temp) o.children.push(temp = { name });
        return temp;
    }, r);
    return r;
}, { children: result });

console.log(result);

Here's what I have so far:

reduce((acc, curr) => {
    return curr.split('/').reduce((obj, name) => {
        let temp = (obj.children != null ? obj.children : obj.children = []).find(node => node.name === name);
        if (temp == null) {
            obj.children.push(temp = {name});
        }
        return temp;
    }, acc);
}, {children: []} as FileTreeNode),

But this is only returning the last node in the observable (eg. {name: 03.txt}). What I want to be passing along is the final value of acc as the above code does. I also would like to accomplish this using just the RxJs operators, instead of having to rely on the internal Js Array.reduce() function if possible.

Thanks!

Upvotes: 1

Views: 466

Answers (1)

Owen Kelvin
Owen Kelvin

Reputation: 15083

Use reduce exactly the same way, The difference is that you need to convert the array to a stream. You can use from operator

  paths = ["path/to/file1.doc", "path/to/file2.doc", "foo/bar.doc"];
  paths$ = from(this.paths).pipe(
    reduce(
      (r, path) => {
        path.split("/").reduce((o, name) => {
          var temp = (o.children = o.children || []).find(q => q.name === name);
          if (!temp) o.children.push((temp = { name }));
          return temp;
        }, r);
        return r;
      },
      { children: [] }
    )
  );

See this demo

Upvotes: 1

Related Questions