272

Is it possible to have an optional route parameter in the Angular 2 route? I tried the Angular 1.x syntax in RouteConfig but received below error:

"ORIGINAL EXCEPTION: Path "/user/:id?" contains "?" which is not allowed in a route config."

@RouteConfig([
{
    path: '/user/:id?',
    component: User,
    as: 'User'
}])
0

14 Answers 14

410

You can define multiple routes with and without parameter:

@RouteConfig([
    { path: '/user/:id', component: User, name: 'User' },
    { path: '/user', component: User, name: 'Usernew' }
])

and handle the optional parameter in your component:

constructor(params: RouteParams) {
    var paramId = params.get("id");

    if (paramId) {
        ...
    }
}

See also the related github issue: https://github.com/angular/angular/issues/3525

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

11 Comments

Correct me if I'm wrong, but this solution worked for me only when the order of routes in the array was reversed, i.e. the route with the parameter occurred before the other one. Until I did that, the router only matched the route without the parameter.
does this solution still apply ? I noticed moving from "User" to "UserNew" will re-instantiate the 'User' component
old but a major problem with this approach is that each route is treated as unique route and it makes component reuse impossible.
As noted by @teleaziz, appending the parameter will re-render the component. To avoid this, Martin Cremer's answer; adding a 'redirectTo' root with a blank parameter value, worked great for me: stackoverflow.com/a/49159166/1364650 - but that's quite hacky, I think they should just support optional route parameters properly.
For those who are wondering why RouteParams not importing to component check this: stackoverflow.com/a/36792261/806202. The solution is to use ActivatedRoute: route.snapshot.params['routeParam']
|
205
{path: 'users', redirectTo: 'users/', pathMatch: 'full'},
{path: 'users/:userId', component: UserComponent}

This way the component isn't re-rendered when the parameter is added.

8 Comments

This answer is the best. It doesn't re-render the same component and it doesn't require multiple components.
The best answer, but I added pathMatch: 'full' to redirect, otherwise paths like users/admin is also redirected in my case
This answer is only the best if you're ok with trailing slashes on your URLs as viewed in the browser. Consider maybe a value that represents 'an undefined id', for example /users/all or /users/home, read the 'all' or 'home' as the id and simply ignore it if it matches your magic value. Then the first line above becomes redirectTo: 'users/home' or whatever you decide. To me a trailing slash really stands out as something being wrong.
@Simon_Weaver I agree. I found another solution using a matcher which doesn't have this problem: stackoverflow.com/a/56391974/664533
it's a simple spell but quite unbreakable :D The best solution!
|
55

It's recommended to use a query parameter when the information is optional.

Route Parameters or Query Parameters?

There is no hard-and-fast rule. In general,

prefer a route parameter when

  • the value is required.
  • the value is necessary to distinguish one route path from another.

prefer a query parameter when

  • the value is optional.
  • the value is complex and/or multi-variate.

from https://angular.io/guide/router#optional-route-parameters

You just need to take out the parameter from the route path.

@RouteConfig([
{
    path: '/user/',
    component: User,
    as: 'User'
}])

2 Comments

Changing optional route params rerenders components, but changing queryParams doesn't. Also if you resolve some data before route navigation, it will be requested each time when you change optional route params.
FYI, that anchor link doesn't work anymore. The new link appears to be Route Parameters: Required or optional?
32

Angular 4 - Solution to address the ordering of the optional parameter:

DO THIS:

const appRoutes: Routes = [
  {path: '', component: HomeComponent},
  {path: 'products', component: ProductsComponent},
  {path: 'products/:id', component: ProductsComponent}
]

Note that the products and products/:id routes are named exactly the same. Angular 4 will correctly follow products for routes with no parameter, and if a parameter it will follow products/:id.

However, the path for the non-parameter route products must not have a trailing slash, otherwise angular will incorrectly treat it as a parameter-path. So in my case, I had the trailing slash for products and it wasn't working.

DON'T DO THIS:

...
{path: 'products/', component: ProductsComponent},
{path: 'products/:id', component: ProductsComponent},
...

7 Comments

If both are going to the ProductsComponent, how do you deal with the optional parameter there?
You can access the :id1, :id2, etc parameters as well as the requested url in ProductsComponent like so: this.route.url.first() .mergeMap((url) => { // console.log('1: url change detected' + url); return this.route.params.do((params) => { // console.log('2: url + params change detected' + params["id1"] + ' ' + params["id2"]); this.id1 = params["id1"]; this.id2 = params["id2"]; }) })
Remember you can pass data to the component too, which can be different for each route even to the same component. Example {path: 'products', component: ProductsComponent, data: { showAllProducts: true} }, could be used and then you check for showAllProducts. A little nicer then checking for a null, but for simpler cases either is probably fine.
Unfortunately, this solution will prevent Angular from reusing the component between products and products/:id. The component will be re-instanciated.
@ObjectiveTC no, Kodiak is right; I have the same problem as them, which is why I landed on this page. I have two routes, /projects and /projects/:id, using the same ProjectsComponent. Being in projects and selecting a project "reloads" the page and re-instantiates the component. :(
|
18

The suggested answers here, including the accepted answer from rerezz which suggest adding multiple route entries work fine.

However the component will be recreated when changing between the route entries, i.e. between the route entry with the parameter and the entry without the parameter.

If you want to avoid this, you can create your own route matcher which will match both routes:

export function userPageMatcher(segments: UrlSegment[]): UrlMatchResult {
    if (segments.length > 0 && segments[0].path === 'user') {
        if (segments.length === 1) {
            return {
                consumed: segments,
                posParams: {},
            };
        }
        if (segments.length === 2) {
            return {
                consumed: segments,
                posParams: { id: segments[1] },
            };
        }
        return <UrlMatchResult>(null as any);
    }
    return <UrlMatchResult>(null as any);
 }

Then use the matcher in your route config:

const routes: Routes = [
    {
        matcher: userPageMatcher,
        component: User,
    }
];

5 Comments

@KevinBeal I have implemented quite a few matchers which do work with AOT. What's the error you are getting here?
Oops. It was something else. My matcher works with AOT.
this is a little bit tricky but the best solution to this problem
IMO, this is the most correct answer to this question. I wish I could give more votes to this answer. I can't believe there aren't more posts about this around the internet. I wish that Angular docs were structured better. Currently you pretty much have to read all their tutorials so you don't miss useful features.
Best answer. Works the best
15

rerezz's answer is pretty nice but it has one serious flaw. It causes User component to re-run the ngOnInit method.

It might be problematic when you do some heavy stuff there and don't want it to be re-run when you switch from the non-parametric route to the parametric one. Though those two routes are meant to imitate an optional url parameter, not become 2 separate routes.

Here's what I suggest to solve the problem:

const routes = [
  {
    path: '/user',
    component: User,
    children: [
      { path: ':id', component: UserWithParam, name: 'Usernew' }
    ]
  }
];

Then you can move the logic responsible for handling the param to the UserWithParam component and leave the base logic in the User component. Whatever you do in User::ngOnInit won't be run again when you navigate from /user to /user/123.

Don't forget to put the <router-outlet></router-outlet> in the User's template.

1 Comment

Avoiding the component being recreated is a good thing if performance is critical. I have another solution which also avoids avoids the component being recreated: stackoverflow.com/a/56391974/664533
11

There are three ways to send route parameter(s) from one component to other component through routes. But first import these libraries in components related .ts files and define in constructor

private route: ActivatedRoute
private router: Router

1st Way: Required routing parameters

//Route Configuration
{path: 'user/:id', component: UserDetailComponent}

//Set Hyperlink/routerLink
<a [routerLink]="['/user', user.id]"></a> 

 //Requesting Url after click on hyperlink
 http://localhost:4200/user/6

//Now you can read id value in navigated component
this.route.snapshot.paramMap.get('id');

2nd Way: Optional path parameters

//Route Configuration
    {path: 'user', component: UserDetailComponent}
    
    //Set Hyperlink/routerLink
    <a [routerLink]=['/user', {name: userName, status: true}]"></a>


//Requesting Url after click on hyperlink
    http://localhost:4200/user;name:userNameValue;status:true

//Now you can read values in navigated component
    this.route.snapshot.paramMap.get('userId');
    this.route.snapshot.paramMap.get('userName');

3rd Way: Optional path parameters

//Route Configuration
    {path: 'user', component: UserDetailComponent}
    
    //Set Hyperlink/routerLink
    <a [routerLink]="['/user']"  [queryParms]="{userId:'911', status:true}"></a>

    //Requesting Url after click on hyperlink
    http://localhost:4200/user?userId=911&status=true

    
    //Now you can read values in navigated component
    this.route.snapshot.paramMap.get('userId');
    this.route.snapshot.paramMap.get('userName');

Reference: https://qastack.mx/programming/44864303/send-data-through-routing-paths-in-angular

1 Comment

I liked more of 3rd way, but the correct snapshot property-mapped for that approach would be queryParamMap (not queryMap as suggested): "this.route.snapshot.queryParamMap.get('userId')"
6

With angular4 we just need to organise routes together in hierarchy

const appRoutes: Routes = [
  { 
    path: '', 
    component: MainPageComponent 
  },
  { 
    path: 'car/details', 
    component: CarDetailsComponent 
  },
  { 
    path: 'car/details/platforms-products', 
    component: CarProductsComponent 
  },
  { 
    path: 'car/details/:id', 
    component: CadDetailsComponent 
  },
  { 
    path: 'car/details/:id/platforms-products', 
    component: CarProductsComponent 
  }
];

This works for me . This way router know what is the next route based on option id parameters.

Comments

3

In Angular 8, ou can simply add the parameter without changing your router config.

Angular Doc Optional param

In yourModule.routing.module.ts

const routes: Routes = [
  { path: 'somePath/:RequiredParam', component: Yourcomponent }
];

In your template :

<div [RouterLink] = ['somePath', requiredParamValue, {optionalParam: value}]></div>

2 Comments

{{ searchTerm }} <a [routerLink]="['/product',product.productId ,{ searchTerm:'Hello'}]"> {{product.productId | ProductTitle:"PROD"}} How can I bind the optional paramater value with component property
works in angular 10 also
2

With this matcher function you can get desirable behavior without component re-rendering. When url.length equals to 0, there's no optional parameters, with url.length equals to 1, there's 1 optional parameter. id - is the name of optional parameter.

  const routes: Routes = [
  {
    matcher: (segments) => {
      if (segments.length <= 1) {
        return {
          consumed: segments,
          posParams: {
            id: new UrlSegment(segments[0]?.path || '', {}),
          },
        };
      }
      return null;
    },
    pathMatch: 'prefix',
    component: UserComponent,
  }]

Comments

1

Ran into another instance of this problem, and in searching for a solution to it came here. My issue was that I was doing the children, and lazy loading of the components as well to optimize things a bit. In short if you are lazy loading the parent module. Main thing was my using '/:id' in the route, and it's complaints about '/' being a part of it. Not the exact problem here, but it applies.

App-routing from parent

...
const routes: Routes = [
  {
    path: '',
    children: [
      {
        path: 'pathOne',
        loadChildren: 'app/views/$MODULE_PATH.module#PathOneModule'
      },
      {
        path: 'pathTwo',
        loadChildren: 'app/views/$MODULE_PATH.module#PathTwoModule'
      },
...

Child routes lazy loaded

...
const routes: Routes = [
  {
    path: '',
    children: [
      {
        path: '',
        component: OverviewComponent
      },
      {
        path: ':id',
        component: DetailedComponent
      },
    ]
  }
];
...

Comments

1

Had the same issue with a Master Detail view. The master view can be visible without the :elementId parameter, but should still be displayed with detail selection open and with the :elementId in the url.

I solved it as follows:

const routes: Routes = [
  {
    path: '',
    component: MasterDetailComponent,
    children: [
      {
        path: ':elementId',
        children: [
          {
            path: 'details',
            component: DetailComponent
          },
          {
            path: '',
            redirectTo: 'details'
          }
        ]
      }
    ]
  }
];

Then in MasterDetailComponent (e.g. in ngOnInit method) you can get the :elementId using the child route:

const childRouteWithElementId = this.route.snapshot.children[0];
const elementIdFromUrl = childRouteWithElementId.params.elementId;
if (!!elementIdFromUrl ) {
  // Do what you need to with the optional parameter
}

Of course you could do the same thing without the child routes and only have the optional elementId at the end of the url.

Comments

0

I can't comment, but in reference to: Angular 2 optional route parameter

an update for Angular 6:

import {map} from "rxjs/operators"

constructor(route: ActivatedRoute) {
  let paramId = route.params.pipe(map(p => p.id));

  if (paramId) {
    ...
  }
}

See https://angular.io/api/router/ActivatedRoute for additional information on Angular6 routing.

Comments

0

Facing a similar problem with lazy loading I have done this:

const routes: Routes = [
  {
    path: 'users',
    redirectTo: 'users/',
    pathMatch: 'full'
  },
  {
    path: 'users',
    loadChildren: './users/users.module#UserssModule',
    runGuardsAndResolvers: 'always'
  },
[...]

And then in the component:

  ngOnInit() {
    this.activatedRoute.paramMap.pipe(
      switchMap(
        (params: ParamMap) => {
          let id: string = params.get('id');
          if (id == "") {
            return of(undefined);
          }
          return this.usersService.getUser(Number(params.get('id')));
        }
      )
    ).subscribe(user => this.selectedUser = user);
  }

This way:

  • The route without / is redirected to the route with. Because of the pathMatch: 'full', only such specific full route is redirected.

  • Then, users/:id is received. If the actual route was users/, id is "", so check it in ngOnInit and act accordingly; else, id is the id and proceed.

  • The rest of the componect acts on selectedUser is or not undefined (*ngIf and the things like that).

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.