Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Angular Router: Empty Paths, Componentless Routes, and Redirects

DZone's Guide to

Angular Router: Empty Paths, Componentless Routes, and Redirects

We look through some code that shows us how to use some advanced pattern-matching with Angular's URL matching engine.

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

At the core of the Angular router lies a powerful URL matching engine which transforms URLs and converts them into router states.

Image title

In this article, I will show how to use three of the engine’s features: empty-path routes, componentless routes, and redirects. And how using them together, we can implement advanced patterns in just a few lines of code.

Empty-Path Routes

Let’s start with this configuration.

[
  {
    path: 'team/:id',
    component: TeamComponent,
    children: [
      {
        path: 'users',
        component: AllUsersComponent
      },
      {
        path: 'user/:name',
        component: UserComponent
      }
    ]
  }
]


When navigating to ’/team/11/user/bob’, the router will instantiate the team component with the user component in it. And when navigating to ’/team/11/users’, the router will show the list of all users. This configuration works. But a more common way of doing this would be to render the list when navigating to /team/11. That’s what empty-path routes are for.

[
  {
    path: 'team/:id',
    component: TeamComponent,
    children: [
      {
        path: '',
        component: AllUsersComponent
      },
      {
        path: 'user/:name',
        component: UserComponent
      }
    ]
  }
]


By setting ‘path’ to an empty string, we can create a route that instantiates a component but does not “consume” any URL segments.

There is nothing really special about such a route. For instance, as any other route, it can have children.

[
  {
    path: 'team/:id',
    component: TeamComponent,
    children: [
      {
        path: '',
        component: WrapperComponent,
        children: [
          {
            path: 'user/:name',
            component: UserComponent
          }
        ]
      }
    ]
  }
]


In this example, when navigating to ’/team/11/user/jim’, the router will instantiate the wrapper component with UserComponent in it.

Matching Strategies and Redirects

To understand the second feature, let’s step back to think about how the router does matching. The router takes an array of routes and a URL, and tries to create a RouterState.

Let’s imagine we have this configuration, and we are navigating to ’/team/11/user/jim’.

[
  {
    path: 'team/:id',
    component: TeamComponent,
    children: [
      {
        path: 'user/:name',
        component: UserComponent
      }
    ]
  }
]


The router will go through the array of routes, which in this case there is only one, and it will check if the URL starts with the route’s path. Here it will check that ’/team/11/user/jim’ starts with 'team/:id’. Because it matches, the router will carry on matching by taking what is left in the URL and the matched route’s children. If the taken path through the configuration does not “consume” the whole url, the router backtracks to try an alternative path.

The default matching strategy is called 'prefix’ because a match is successful when a route’s path is the prefix of what is left in the URL. We can set the strategy explicitly, as follows:

[
  {
    path: 'team/:id',
    pathMatch: 'prefix',
    component: TeamComponent,
    children: [
      {
        path: 'user/:name',
        pathMatch: 'prefix',
        component: UserComponent
      }
    ]
  }
]


The router supports a second matching strategy–full, which checks that the path is “equal” to what is left in the URL. This is mostly important for redirects. To see why, let’s look at the this example:

[
  {
    path: '', //default pathMatch: 'prefix'
    redirectTo: 'teams'
  },
  {
    path: 'teams',
    component: TeamsComponent
  }
]


Most likely your intent is to render the Teams component when navigating to ’/teams’, and redirect to ’/teams’ when navigating to ’/’. But since the default matching strategy is 'prefix’, and an empty string is a prefix of any URL, the router will apply the redirect even when we are navigating to ’/teams’. Now, if we change the matching strategy to 'full’, the router will apply the redirect only when navigating to ’/’.

[
  {
    path: '',
    pathMatch: 'full',
    redirectTo: 'teams'
  },
  {
    path: 'teams',
    component: TeamsComponent
  }
]


Absolute Redirects

If the 'redirectTo’ value starts with a ’/’, then it is an absolute redirect. The next example shows the difference between relative and absolute redirects.

[
  {
    path: 'team/:id',
    component: TeamComponent,
    children: [
      {
        path: 'org/:name',
        redirectTo: '/org/:name'
      },
      {
        path: 'legacy/user/:name',
        redirectTo: 'user/:name'
      },
      {
        path: 'user/:name',
        component: UserComponent
      }
    ]
  },
  {
    path: 'org/:name',
    component: OrgComponent
  }
]


When navigating to ’/team/11/legacy/user/jim’, the router will apply the second redirect and will change the URL to ’/team/11/user/jim’. In other words, the part of the URL corresponding to the matched path will be replaced. But navigating to ’/team/11/org/eng’ will replace the whole URL with ’/org/eng’.

Componentless Routes

It is often useful to share parameters between sibling components. In this example, we have two components—TeamListComponent and TeamDetailsComponent—that we want to put next to each other, and both of them require the team id parameter. TeamListComponent uses the id to highlight the selected team, and TeamDetailsComponent uses it to show the information about the team.

One way to model that would be to create a bogus parent component, which both TeamListComponent and TeamDetailsComponent can get the id parameter from, i.e. we can model this solution with the following configuration:

[
  {
    path: 'team/:id',
    component: TeamParentComponent,
    children: [
      {
        path: '',
        component: TeamListComponent
      },
      {
        path: '',
        component: TeamDetailsComponent,
        outlet: 'details'
      }
    ]
  }
]


With this configuration in place, navigating to ’/team/11’ will result in this component tree:

AppComponent -> TeamParentComponent -> TeamListComponent
                                    -> TeamDetailsComponent


This solution has two problems. First, we need to create the team parent component, which serves no real purpose. Second, TeamListComponent and TeamDetailsComponent have to access the id parameter through the parent, which makes them less reusable. Because this use case is so common, the router supports a feature called componentless routes.

Componentless routes “consume” URL segments without instantiating components.

Let’s change the 'team/:id’ route to make it Componentless.

[
  {
    path: 'team/:id',
    children: [
      {
        path: '',
        component: TeamListComponent
      },
      {
        path: '',
        component: TeamDetailsComponent,
        outlet: 'aux'
      }
    ]
  }
]


Now, when navigating to ’/team/11’, the router will create the following component tree:

AppComponent -> TeamListComponent
             -> TeamDetailsComponent


Why is this better? First of all, we got rid of the bogus component. Second, since there is no component associated with the ’/team/:id’ route, the router will merge its params, data, and resolve into the children. As a result, TeamListComponent and TeamDetailsComponent can access the id parameter directly, without going through the parent route.

Composing Empty-Path Routes, Componentless Routes, and Redirects

What is really exciting about all these features is that they compose very nicely. And we can use them together to implement advanced patterns in just a few lines of code.

Let me give you an example. We’ve learned that we can use empty-path routes to instantiate components without consuming any URL segments, and we can use Componentless routes to consume URL segments without instantiating components. What about combining them?

[
  {
    path: '',
    canActivate: [CanActivateTeamsOrOrgs],
    resolve: {
      token: TokenNeededForBothTeamsAndOrgs
    },

    children: [
      {
        path: 'teams',
        component: TeamsComponent
      },
      {
        path: 'orgs',
        component: OrgsComponent
      }
    ]
  }
]


Here we’ve defined a route that neither consumes any URL segments nor creates any components, but is used merely for running guards and fetching data that will be used by both TeamsComponent and OrgsComponent.

Next example, although contrived, shows how we can use the three features to implement interesting URL transformations.

[
  {
    path: 'team/:id',
    children: [
      { path: '', pathMatch: 'full', redirectTo: 'list' },
      { path: 'list', component: TeamListComponent,
        children: [
       { path: '', pathMatch: 'full', redirectTo: 'default' },
       { path: 'default', component: DefaultComponent }
        ]
      },
      { path: '', pathMatch: 'full', redirectTo: 'details' outlet: 'aux' }
    ]
  }
]


With this configuration in place, the navigation to 'team/11’ will result in 'team/11/(list/default//aux:details).

Learn More

  • Read Angular Router to get an overview of how the router works.
  • Follow Victor on twitter to learn more about the router
  • Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

    Topics:
    angular

    Published at DZone with permission of

    Opinions expressed by DZone contributors are their own.

    {{ parent.title || parent.header.title}}

    {{ parent.tldr }}

    {{ parent.urlSource.name }}