Jeroen
Jeroen

Reputation: 3583

Angular 2 optional route parameter

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'
}])

Upvotes: 270

Views: 274141

Answers (14)

Muhammad Bilal
Muhammad Bilal

Reputation: 3008

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

Upvotes: 11

Nas3nmann
Nas3nmann

Reputation: 500

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.

Upvotes: 1

Yanis Yakuza
Yanis Yakuza

Reputation: 131

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>

Upvotes: 3

Denis R
Denis R

Reputation: 276

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,
  }]

Upvotes: 2

Martin Cremer
Martin Cremer

Reputation: 5572

{path: 'users', redirectTo: 'users/', pathMatch: 'full'},
{path: 'users/:userId', component: UserComponent}

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

Upvotes: 198

Ravi Jadhav
Ravi Jadhav

Reputation: 81

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.

Upvotes: 6

Javier Sedano
Javier Sedano

Reputation: 953

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).

Upvotes: 0

Wayne Maurer
Wayne Maurer

Reputation: 12581

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,
    }
];

Upvotes: 16

mschreib28
mschreib28

Reputation: 1

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.

Upvotes: 0

Jp_
Jp_

Reputation: 6173

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'
}])

Upvotes: 54

matewka
matewka

Reputation: 10148

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.

Upvotes: 15

L.P.
L.P.

Reputation: 406

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
      },
    ]
  }
];
...

Upvotes: 1

ObjectiveTC
ObjectiveTC

Reputation: 2507

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},
...

Upvotes: 32

rerezz
rerezz

Reputation: 4210

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

Upvotes: 408

Related Questions