【问题标题】:RXJS 6 : Recursive filtering of an array .. with an async filterRXJS 6:使用异步过滤器对数组进行递归过滤
【发布时间】:2019-06-26 07:16:45
【问题描述】:

我需要过滤一个递归对象数组。 每个对象代表一个 webapp 路由/url。这个url可以被限制为某个角色(permission=true|false),每个url都可以有子url...递归。

编辑: 复杂的部分是过滤需要一个异步函数调用(我的项目中有这个特定需求)。 这就是为什么我尝试用 RXJS 来做,但我可以用标准数组函数 + async/await 来做......

我也借此机会学习了更多 rxjs,这就是为什么我想要一个面向 rxjs 的答案(它处理异步,这是一个好方法吗?)。谢谢

拥有这个数组:

[
      {
        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
      }
    ]

我需要过滤它以获得类似的输出(只保留没有权限或真实的条目:

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

我尝试的没有递归(注释代码),所以第一级过滤成功,但我不知道如何添加递归:

// 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)))

这里是小提琴:https://stackblitz.com/edit/angular6-rxjs6-playground-idysbh?file=app/hello.component.ts

【问题讨论】:

  • 嗨 Cétia,到目前为止,我仍然对您尝试实现的目标感到困惑。您能描述一下您的功能流程吗?听起来您有没有permission 字段的嵌套对象,并且您必须实现ajax 请求才能请求此标志,然后使用此新信息过滤您的原始列表。另一个问题:是否可以在执行任何流程之前将您的列表变平?
  • 你描述的没错。不可能展平,这是构建一个组=>子菜单
  • 在我看来,rxjs 中的递归并不是那么自然。有一个名为expand 的运算符可用于满足您的需求。我前段时间写了一个类似问题的答案,看看:stackoverflow.com/questions/53129852/…
  • 也是不可能的,但我强烈建议您避免多次请求授权。对我来说,您应该解析您的对象并搜索每个必须检查的项目,然后执行单个请求,再次解析您的对象并设置授权标志。

标签: angular asynchronous filter rxjs


【解决方案1】:

我不明白你为什么需要 RxJS 来处理你的列表。

我提出了这个实现:

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));

如果你想在 rxjs 流上打开这个列表,你可以简单地使用 map

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

编辑一个:

基于澄清,我在 Observable 方式上完成了上面相同的代码。

目标是拥有 Observable 工厂函数(这里是allowOnly$):

  • 创建将广播当前数组的每个项目的流。
  • concatMap 带有 ajax 请求的此项目。
  • filter 不允许的项目。
  • concatMap 又是新的combineLatest,它们是当前项和allowOnly$ 的递归调用的组合,所有子项作为参数。
  • toArray 将我们当前的项目流转换回单个广播,所有项目合并到数组上。

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);

重要说明所有mergeMap 都切换到concatMap 以尊重原始列表顺序,而不是将所有基于ajax 请求答案的项目混在一起。

【讨论】:

  • 感谢您的帮助。我编辑了我的答案。关键部分是我的过滤需要是异步的(我添加了 Promise 函数),这就是我尝试使用 rxjs 的原因.. + 我想学习如何使用 rxjs 概念的事实,这就是我迷失的地方
猜你喜欢
  • 2016-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多