1

I need to filter a recursive array of objects. Each object represent a webapp route/url. This url can be limited to a certain role (permission=true|false) or not, and each url can have child urls ... recursively.

EDIT: The complicated part is that the filtering needs an asynchronous function call (I have that specific need in my project). That's why I tried to do it with RXJS but I could have done it with standard array functions + async/await...

I also took this opportunity to learn some more rxjs, that's why I want a rxjs-oriented answer (and this it deals with async, it's a good way hu?). Thanks

Having this array :

[
      {
        id: 'level 1.1',
        permission: true,
        children: [
          {
            id: 'level 2.1',
            permission: false,
            children: [
              {id: 'level 3.1'}
            ]
          },
          {
            id: 'level 2.2',
            permission: true,
            children: [
              {id: 'level 3.2'}
            ]
          }
        ]
      },
      {
        id: 'level 1.2'
      },
      {
        id: 'level 1.3',
        permission: false
      }
    ]

I need to filter it to have an output like (keep only entries where permission is absent or truthy :

[
      {
        id: 'level 1.1',
        permission: true,
        children: [
          {
            id: 'level 2.2',
            permission: true,
            children: [
              {id: 'level 3.2'}
            ]
          }
        ]
      },
      {
        id: 'level 1.2'
      }
    ]

What I tried works without the recursion (commented code), So the first level is filtered successfully, but I don't know how to add the recursion :

// simplified ASYNC filter function
promiseMe(x) {
    return Promise.resolve().then(() => {
      return x.permission === undefined || x.permission === true
    });
}

// recursive function
const recursive = arr => {
    return from(arr).pipe(
        mergeMap(entry => from(this.promiseMe(entry)).pipe(
            tap(y => console.log(y)),
            filter(Boolean),
            mapTo(entry),
            tap(console.log),
            mergeMap(item => {
                // here I'm lost
                // I need to affect the result of my async recursive function to item.children : 
              /*return recursive(item.children).pipe(
                  tap(res => {
                    console.log('RES', item, res)
                    item.children = res;
                  })
                );*/

                return of(item);
            })
        )),
        toArray()
    )
};

// main call
recursive(arr).subscribe(x => console.log('finally', x, JSON.stringify(x)))

FIDDLE here : https://stackblitz.com/edit/angular6-rxjs6-playground-idysbh?file=app/hello.component.ts

4
  • hi Cétia, i am still confus about what you try to achieve so far. Could you describe your functional process ? Sounds like you have nested object without permission field, and you have to achieve ajax request to ask for this flag, then filter your original list with this new information. Another question : is it possible to flat your list before to do any process ? Commented Feb 3, 2019 at 7:29
  • What you describe is right. No flatten possible, it's to construct a group=>child menu Commented Feb 3, 2019 at 15:40
  • recursion in rxjs doesn't come quite as naturally in my point of view. there is an operator called expand that is used for your needs. I wrote an answer to a similar question some time ago, check it out: stackoverflow.com/questions/53129852/… Commented Feb 3, 2019 at 18:13
  • is also out of the question, but i highly recommand you to avoid multiple request to ask for authorization. For me, you should parse your object and search each item which have to be checked, then do single request, parse again your object and set the authorization flag. Commented Feb 4, 2019 at 5:01

1 Answer 1

3
+100

I can't figure out why you need RxJS to process your list.

I propose this implementation :

const source = [
    {
      id: 'level 1.1',
      permission: true,
      children: [
        {
          id: 'level 2.1',
          permission: false,
          children: [
            {id: 'level 3.1'}
          ]
        },
        {
          id: 'level 2.2',
          permission: true,
          children: [
            {id: 'level 3.2'}
          ]
        }
      ]
    },
    {
      id: 'level 1.2'
    },
    {
      id: 'level 1.3',
      permission: false
    }
];

const isAllow = item => {
  return item.permission === undefined || item.permission;
};

const filtering = (list) => {
  const listing = [];
  list.forEach(item => {
    // If current one have permission.
    if(isAllow(item)) {
      // If he have child, let process it recursively.
      if(item.children && item.children.length > 0) {
        item.children = filtering(item.children);
      }
      // Add current on to whitelisted.
      listing.push(item);
    }
  });
  return listing;
};

console.log(filtering(source));

if you want to turn on this list on rxjs stream, you can simply use map :

of(source).pipe(map(source => filtering(source))).subscribe(console.log)

EDIT ONE :

Base on clarification, i have done the same code above on Observable way.

Goal is to have Observable factory function (here is allowOnly$) which :

  • create stream where each item of your current array will be broadcast.
  • concatMap this item with ajax request.
  • filter item which are not allowed.
  • concatMap again new combineLatest which are the combine of current items and recursive call of allowOnly$ with all childrens as parameters.
  • toArray to transform back our current stream of item to single broadcast with all items merge on array.

Voilà

const dummyAjaxRequest = (item) => {
  return of({
      ...item,
      permission: (item.permission === undefined || item.permission)?true:false
      });
}

const allowOnly$ = items => {
  return from(items).pipe(concatMap(item => {
    return from(
      /**
       * Perform your ajax request here to find what's is allow or not.
       */
      dummyAjaxRequest(item)
    ).pipe(
      /**
       * Exclude what is not allowed;
       */
      filter(item => item.permission),
      concatMap(item => {
        /**
         * If we have child, perform recursive.
         */
        if (item.children) {
          /**
           * combine child and parent.
           */
          return combineLatest(
            allowOnly$(item.children), // Recursive call.
            of(item)
          ).pipe(map(i => {
            return {
              ...i[1], // all property of current,
              children : [...i[0]] // Create new array base on allowed childrens.
            };
          }))
        }
        else {
          /**
           * No child, return simple observable of current item.
           */
          return of(item);
        }
      })
    );
  }), toArray()); // transform stream like --|-|-|-> to --[|,|,|]->
};

of(source).pipe(concatMap(items => {
  return allowOnly$(items);
})).subscribe(console.log);

Important note All mergeMap are switch to concatMap to respect the original list order instead of mix up all items base on which have the ajax request answer first.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your help. I edited my answer. The critical part is that my filtering needs to be async (I added the promise function), that's why I tried with rxjs.. that + the fact that I wanted to learn how to use rxjs concepts, and that's where I'm lost

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.