3

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!

1 Answer 1

4

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.

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

Comments

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.