All files / services/search search.service.ts

8.82% Statements 3/34
0% Branches 0/31
0% Functions 0/8
6.25% Lines 2/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95                          2x   2x                                                                                                                                                              
import { Injectable } from '@angular/core';
import { Data, Route, Routes } from '@angular/router';
 
/** Parsed application route. */
export interface IParsedRoute {
  path: string;
  outlet?: string;
  data?: Data;
}
 
@Injectable({
  providedIn: 'root',
})
export class AppSearchService {
  /** Route filtering logic for the application search. */
  readonly #filters = Object.freeze({
    /** Filters routes with empty paths that do not have a `feature` property defined in their `data` object. */
    notEmptyWithData: (route: IParsedRoute) =>
      route.path !== '' || (route.path === '' && typeof route.data !== 'undefined' && 'feature' in route.data),
    /** Filters routes containing a `skipSearch` instruction which value is `true` in their `data` object. */
    noExplicitSkipSearch: (route: IParsedRoute) =>
      !(typeof route.data !== 'undefined' && 'skipSearch' in route.data && route.data['skipSearch'] === true),
  });
 
  /**
   * The main route filtering logic.
   * @param route Parsed application route.
   */
  public routeFilter(route: IParsedRoute): boolean {
    return (
      this.#filters.notEmptyWithData(route) &&
      this.#filters.noExplicitSkipSearch(route) &&
      !route.path.includes('*') &&
      !route.path.includes(':') &&
      typeof route.outlet === 'undefined' &&
      route.path !== 'offline' &&
      route.path !== 'error'
    );
  }
 
  /**
   * Child route configuration parser.
   * @param root root path / parent path
   * @param data root path data / parent path data
   * @param outlet router outlet for the given route
   * @param routes child routes
   * @returns the array with routes, route segment separator is space
   */
  #parseChildRoutes(root = '', data?: Data, outlet?: string, routes?: Routes): IParsedRoute[] {
    return typeof routes === 'undefined'
      ? [{ path: root.trim(), data, outlet }]
      : routes.flatMap(child => {
          const childPath = `${root} ${child.path}`.trim();
          const childData = child.data;
          const childOutlet = child.outlet;
          return this.#parseChildRoutes(childPath, childData, childOutlet, child.children);
        });
  }
 
  /**
   * Router configuration parser.
   * @param root root route
   * @returns the array with routes, route segment separator is space
   */
  public async parseRoute(root: Route): Promise<IParsedRoute[]> {
    const route = { ...root };
    const rootPath = (route.path ?? '').trim();
    const result: IParsedRoute[] = [{ path: rootPath, data: root.data, outlet: route.outlet }];
    let children: Routes = route.children ?? [];
    if (children.length === 0 && typeof route.loadChildren !== 'undefined') {
      await route.loadChildren();
      const loadedRoutes = (route as Route & Record<'_loadedRoutes' | string, Route['children']>)['_loadedRoutes'];
      children = typeof loadedRoutes !== 'undefined' ? [...loadedRoutes] : children;
    }
    const resolvers = children.flatMap(async child => {
      const childPath = `${rootPath} ${child.path}`.trim();
      const childData = child.data;
      const childOutlet = child.outlet;
      const expandRoutes = this.#parseChildRoutes(childPath, childData, childOutlet, child.children);
      result.push(...expandRoutes);
      return result;
    });
    if (typeof resolvers !== 'undefined') {
      await Promise.all(resolvers);
    }
    return result.reduce((accumulator: IParsedRoute[], record) => {
      const exists = accumulator.findIndex(item => item.path === record.path);
      if (exists === -1) {
        accumulator.push(record);
      }
      return accumulator;
    }, []);
  }
}