29

Is there a way to optimize this code from this

{
  path: 'access-requests',
  canActivate: [AccessGuard],
  component: AccessRequestsComponent,
  children: [
    {
      path: '',
      redirectTo: 'today',
      pathMatch: 'full'
    },
    {
      path: 'today',
      component: AccessRequestsComponent
    },
    {
      path: 'tomorrow',
      component: AccessRequestsComponent
    },
    {
      path: 'expired',
      component: AccessRequestsComponent
    }
    ]
}

to something like this

{
  path: 'access-requests',
  canActivate: [AccessGuard],
  component: AccessRequestsComponent,
  children: [
    {
      path: '',
      redirectTo: 'today',
      pathMatch: 'full'
    },
    {
      path: 'today | tomorrow | expired',
      component: AccessRequestsComponent
    }
    ]
}
1
  • may be it is possible using regex Commented Sep 22, 2017 at 12:52

3 Answers 3

30

You can use the UrlMatcher property.

    {
      path: 'access-requests',
      canActivate: [AccessGuard],
      component: AccessRequestsComponent,
      children: [
        {
          path: '',
          redirectTo: 'today',
          pathMatch: 'full'
        },
        {
          matcher: matcherFunction,
          component: AccessRequestsComponent
        }
        ]
    }

And

    export function matcherFunction(url: UrlSegment[]) {
        if (url.length == 1) {
            const path = url[0].path;
             if(path.startsWith('today') 
               || path.startsWith('tomorrow') 
               || path.startsWith('expired')){
              return {consumed: url};
            }
        }
        return null;
    }

Note: Untested code

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

3 Comments

Yes, one could use something like this. We can see how the defaultUrlMatcher does a path match as an example.
this is the correct answer, when you use this approach ... this.router.navigateByUrl does not try and reload the component when switching between the 2 (or more) paths
You could simplify the chain of || operators by using ['today', 'tomorrow'].some(p => path.startsWith(p)).
30

You can use a mapped array:

children: [
  // excluding the other paths for brevity
  ...['today', 'tomorrow', 'expired'].map(path => ({
    path,
    component: AccessRequestsComponent
  }))
]

5 Comments

I realize the above is probably better practice, but this seems more readable to me.
@DaneBrouwer thanks for the feedback! Regarding the "better practice" part: although more generic, the approach with the url matcher is slower, more intricate and not necessary (as there's no upside in our case). I think K.I.S.S. is a great practice
Honestly I didn't even think about the implications of using a custom url matcher - it just felt more Angular-like to me. However, I can't argue with K.I.S.S. or performance optimizations!
Note that this method will cause Angular to reload the component when switching between the different paths, which might or might not be what you want.
For me, It's showing the error: ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'split' of undefined TypeError: Cannot read property 'split' of undefined at defaultUrlMatcher (router.js:525) I'm using v9.
1

Building on CornelC's answer, I wrote a method that accepts an array of string as paths and spits out a url matcher that will match an element of the array:

    export const routingMatcher: ((paths: string[]) => UrlMatcher) = (paths: string[]) => {
      const result: UrlMatcher = (segments) => {
        const matchingPathIndex = paths.findIndex((path, index) => {
          const pathSegments = path.split("/");
          return segments.every((segment, i) =>
            pathSegments.length > i && (
              pathSegments[i].startsWith(":") ? true : segment.path.toLowerCase() === pathSegments[i].toLowerCase()));
        });
    
        if (matchingPathIndex >= 0) {
          const matchingPath = paths[matchingPathIndex];
    
          const consumed: UrlSegment[] = [];
          const params = {};
    
          matchingPath.split("/").forEach((path, i) => {
            consumed.push(segments[i]);
            if (path.startsWith(":")) {
              const param = path.substring(1);
              params[param] = segments[i];
            }
          });
    
          return {
            consumed: consumed,
            posParams: params
          };
        }
    
        return null;
      };
    
      return result;
    };

You can use it like this in your routing definitions:

    children: [
      // excluding the other paths for brevity
        matcher:  routingMatcher(['today', 'tomorrow', 'expired'])
        component: AccessRequestsComponent
      }))
    ]

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.