amcdnl
amcdnl

Reputation: 8638

RxJS Filter Array of Arrays

I'm trying to perform a filter on a array of arrays in rxjs. Consider the following:

    function guard1(): boolean | Observable<boolean> {}
    function guard2(): boolean | Observable<boolean> {}
    function guard3(): boolean | Observable<boolean> {}

    const routes = [
        { name: 'Foo', canActivate: [guard1, guard2] },
        { name: 'Bar', canActivate: [guard3] },
        { name: 'Moo' }
    ];

I want to filter the routes array to only routes that return true from the combination of results of inner array canActivate or if it doesn't have canActivate, I want it to be NOT filtered out.

Lets say guard1 returned true and guard2 returned false, I'd expect route Foo to not be in the filtered list.

I took a stab at this but its not quite doing what I expect:

    this.filteredRoutes = forkJoin(of(routes).pipe(
            flatMap((route) => route),
            filter((route) => route.canActivate !== undefined),
            mergeMap((route) =>
                of(route).pipe(
                    mergeMap((r) => r.canActivate),
                    mergeMap((r) => r()),
                    map((result) => {
                        console.log('here', result, route);
                        return route;
                    })
                )
        )));

If I were writing this outside of RXJS, the code might look something like this:

    this.filteredRoutes = [];
    for (const route of this.routes) {
        if (route.canActivate) {
            let can = true;
            for (const act of route.canActivate) {
                let res = inst.canActivate();
                if (res.subscribe) {
                    res = await res.toPromise();
                }
                can = res;
                if (!can) {
                    break;
                }
            }
            if (can) {
                this.filteredRoutes.push(route);
            }
        } else {
            this.filteredRoutes.push(route);
        }
    }

Thanks!

Upvotes: 3

Views: 1086

Answers (1)

Brocco
Brocco

Reputation: 64863

I'm sure there's other (and likely better ways to handle this, but it works...

from(routes).pipe(
    concatMap((route) => {
      // handle if nothing is in canActivate
      if (!route.canActivate || route.canActivate.length === 0) {
        // create an object that has the route and result for filtering
        return of({route, result: true})
      };

      const results = from(route.canActivate).pipe(
        // execute the guard
        switchMap(guard => {
          const result: boolean | Observable<boolean> = guard();
          if (result instanceof Observable) {
            return result;
          } else {
            return of(result);
          }
        }),
        // aggregate the guard results for the route
        toArray(),
        // ensure all results are true
        map(results => results.every(r => r)),
        // create an object that has the route and result for filtering
        map(result => ({route, result})),
      );

      return results;
    }),
    // filter out the invalid guards
    filter(routeCanActivateResult => routeCanActivateResult.result),
    // return just the route
    map(routeCanActivateResult => routeCanActivateResult.route),
    // turn it back into an array
    toArray()
  )
  // verify it works
  .subscribe(routes => routes.forEach(r => console.log(r.name)));

Also, here is a working example in stackblitz.

Upvotes: 4

Related Questions