51

Let's suppose I've got 3 urls: /:projectId/info, /:projectId/users, /:projectId/users/:userId/profile. All of them have param projectId. UI has a component to switch from one project to another. So I need:

  1. Get current URL
  2. Change param by name (e.g. projectId)
  3. Navigate to new url

So I need something like this.router.replaceParam(currentUrl, {projectId: 'project_id_2'}) which will transform /project_id_1/users/user_id_1/profile into /project_id_2/users/user_id_1/profile (and any other URL with :projectId param)

I thought it's a simple and common problem but didn't find a solution in 1 hour. Suggested here solution doesn't work as noted in the last comment

fedor.belov
  • 22,343
  • 26
  • 89
  • 134
  • Not sure what the problem is. if you're at that URL, the routed component probably already uses the projectId (otherwise this parameter would be useless, wouldn't it?), so just use `this.router.navigate([this.projectId, 'users'])` for example. – JB Nizet Jun 10 '18 at 14:06
  • @JBNizet I updated my question, please check is it clear? – fedor.belov Jun 10 '18 at 19:24
  • @fedor.belov have you tried router.navigate(['/'+yourprojectId + '/users/' + youruserId + '/profile' ]) ? – CruelEngine Jun 13 '18 at 13:16

13 Answers13

34

To navigate to particular link from current url, you can do something like this,

 constructor(private route: ActivatedRoute, private router: Router){}
 ngOnInit() {
     this.route.params.subscribe(params => {
         // PARAMS CHANGED ..    

         let id = params['projectid'];    
     });
 }
 navigate(){
     this.router.navigateByUrl(this.router.url.replace(id, newProjectId));
     // replace parameter of navigateByUrl function to your required url
 }

On ngOnInit function, we have subscribed to params so we can observe and executes our statements on any change in url parameter.

Edit

Case: where ids can be same

constructor(private route: ActivatedRoute, private router: Router){}
     projectId:string;
     userId: string;
     ngOnInit() {
         this.route.params.subscribe(params => {
             this.projectId = params['projectid']; 
             this.userId = params['userId'];
         });
     }
     navigate(){
         // for /project/:projectId/users/:userId/profile
         this.router.navigate(['/project', this.projectId, '/users', 
         this.userId, '/profile']);
     }
khush
  • 2,702
  • 2
  • 16
  • 35
  • 17
    This is the closest solution but I can't accept it because it requires unique IDs in URL – fedor.belov Jun 20 '18 at 16:34
  • 1
    Can you please brief more about what exactly you are trying to do? – khush Jun 21 '18 at 06:34
  • 2
    if you rute is 'location/:id/business/:id' and the two ids are 1, and I want only change the secondOne, how work your solution? – juanjinario Feb 28 '20 at 11:50
  • 2
    This is doing string.replace? ... What if you url is `/account/123/project/123` wont this mess up stuff.. this is just assuming the id will be unique across the whole url string. – nawlbergs Jun 11 '20 at 15:52
  • @nawlbergs, In that you have to keep name of route parameters different. – khush Jun 11 '20 at 16:21
  • 2
    -1 Even if the param name is different, it's faulty. e.g.: If the route params are `/:projectId/users/:userId` and the id of the project happens to be the same as the id of the user, it will replace both, which is not desired. – Nullius Jan 23 '21 at 18:29
  • check edited answer for the case where ids can be same – khush Jun 15 '21 at 05:33
8

You can use either HTML Or Ts

1 > In HTML

[routerLink]="['../info']"
        Or
[routerLink]="['../users']"
    like this etc....

2 > In Typescript

this.router.navigate(['../users'], { relativeTo: this.activatedRoute });
Omkar Jadhav
  • 656
  • 1
  • 5
  • 18
  • using your solutions in a header doesnt work if you are in a route `:projectId/items/:itemId` and neither does it work when coming from a base route `/:projectId` -> `Navigate to ../items`-> `/items` instead of the desired `/:projectId/items` – Jeremias Nater Sep 22 '21 at 13:07
5

You can use:

this.router.navigate(
      [],
      {
        relativeTo: this.activatedRoute,
        queryParams: {projectId: 'project_id_2'},
        queryParamsHandling: "merge", // remove to replace all query params by provided
      });
Gerard Carbó
  • 1,775
  • 18
  • 16
3

I faced today this problem with angular 9. In my case, i had three url-scenario:

  1. something/i-ignore/../my-path where i display a predefined value, (e.g.: latest value)
  2. something/i-ignore/../my-path -> something/i-ignore/../my-path/1 where i explicitly require the /1 resource starting from the basic url
  3. something/i-ignore/../my-path/1 -> something/i-ignore/../my-path/2 where i change the resource i'm requesting

An interesting approach is the one proposed by Omkar Jadhav where he programmatically goes a step backwards from the activatedRoute

this.router.navigate(['../users'], { relativeTo: this.activatedRoute });

and I reacted to the parameter change event with a simplified version of the code proposed by Michael

this._route.params
  .subscribe((params) => {
    console.log(params) // prints {resourceId: "1"}
  });

Combining everthing said above, I can begin my navigation handling both case 2. and 3.

const url = this.router.url.split('my-path');
if (url[1] && 0 < url[1].length) {
  this.router.navigate(['../' + this.newResourceId],{relativeTo: this.activatedRoute});
} else {
  this.router.navigate([this.newResourceId], {relativeTo: this.activatedRoute});
}

and then i just have to subscribe to the params

this.activatedRoute.params.subscribe(params => this.getResource(params['resourceId']));

Full code:

ngOnInit(): void {
    this.activatedRoute.params.subscribe(params => this.getResource(params['resourceId']));
    this.getResource(this.activatedRoute.snapshot.params?.resourceId);
}

resourceRequestEvent(resourceId: number) {
    // this will trigger the page refresh
    const url = this.router.url.split('my-path');
    if (url[1] && 0 < url[1].length) {
        this.router.navigate(['../' + resourceId], {relativeTo: this.activatedRoute});
    } else {
        this.router.navigate([resourceId], {relativeTo: this.activatedRoute});
    }
}
Biiz
  • 349
  • 4
  • 18
  • Ended up doing something similar. Except splitting on slash. Figured out the place of the Id with indexOf the leading path, and replaced it. Makes more sense to me in context of the question. Don't think relativeTo would work only changing the first ID. And now you can just pass the resulting list into navigate. – Allcor May 20 '22 at 13:51
1

Looking at your question , you want to change the 2 parameters .

As stated in :

https://angular.io/api/router/Router#navigatebyurl

You can implement router.navigate([yourprojectId, 'users', youruserId , 'profile'], {relativeTo: route});

CruelEngine
  • 2,701
  • 4
  • 23
  • 44
  • 16
    I don't know what is a current page (`/:projectId/info`, `/:projectId/users` or any other page) / I don't know URL structure of the current page (it may have any other additional parram). I just know that it has param `:projectId` and I want to replace `:projectId` from `A` to `B` – fedor.belov Jun 13 '18 at 14:27
  • hi @fedor.belov didi you find the solution for this , i have the same feature to implement? can u help me with your solution? – DpGp Jun 11 '19 at 07:36
1

Using Angular 7, I achieved this by using a service that stores the current state of the router on every NavigationEnd. I can then traverse the state tree and construct an array of paths that can be used later to find and replace a param like :projectId.

Getting path array:

constructor(private router: Router) {
    this.router.events.subscribe(event => {
        if (event instanceof NavigationEnd) {
            this.pathArray = this.getPathArray(this.router.routerState.snapshot.root);
        }
    }
}

getPathArray(route: ActivatedRouteSnapshot) {
    let array = [];

    if (route.routeConfig && route.routeConfig.path !== '') {
        array.push(route.routeConfig.path);
    }

    if (route.firstChild) {
        array = array.concat(this.getPathArray(route.firstChild));
    }

    return array;
}

Replacing :projectId:

replacePathArrayId(id) {
    return this.pathArray.map(path => {
        return path.replace(':projectId', id);
    })
}

And using router.navigate(service.replacePathArrayId(id)) to actually change the route.

k0nG
  • 4,716
  • 2
  • 20
  • 18
1

This service will rebuild the current url using the current route config. It works by rebuilding the url segments and replacing segments that start with:with matching keys in the params

To update the current url you would simply call the method

this.routingService.updateRoute({id: 123})
import { Injectable } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { filter } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class RoutingService {
  routeChange: Subject<void> = new Subject<void>();
  params: Params;

  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute
  ) {
    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .subscribe(() => {
        this.params = this.activatedRoute.firstChild.snapshot.params;
        this.routeChange.next();
      });
  }


  updateRoute(data: { [key: string]: string | number }) {
    const params = { ... this.params, ...data };
    const segments = this.activatedRoute.firstChild.snapshot.routeConfig.path.split('/');
    const commands = segments.map(v => v.indexOf(':') === 0 ? params[v.substr(1)] : v);
    if (commands.join('/') !== this.activatedRoute.firstChild.snapshot.url.join()) {
      return this.router.navigate(commands);
    }
  }
}
0x6563
  • 1,032
  • 10
  • 17
1

This will take a url string and update all the parameters that are in the params object:

constructor(private route: ActivatedRoute, private router: Router){}

setRouteParams(url: string, route: ActivatedRoute, params: any): string {
  for (const p in params)  {
    const old = route.snapshot.paramMap.get(p);
    url = url.replace(`/${old}/`, `/${params[p]}/`);
  }
  return url;
}

It iterates over the properties of params gets the current value of each property p from the current route then replaces it with the value of the property params[p]. We want to know the current route param value so we know what needs replacing. Matching /${old}/ instead of old will avoid situations like wanting to replace /aa/ with /bb/ in /aad/aa/ but getting /bbd/aa/ instead.

It can be called like this setRouteParams('/project_id_1/users/user_id_1/profile', this.activatedRoute, {projectId: 'project_id_2').

This will not handle routes like /11/users/11/profile/ with params {userId: '22'}. It will replace the projectId instead. To handle that situation we need to know the order of the params {userId: {value: '22', position: 3}} (1 based position because the first segment below will be the empty string).

setRouteParams(url: string, params: any): string {
  const segments = url.split('/');
  for (const p in params)  {
    segments[params[p].position] = params[p].value;
  }
  return segments.join('/');
}

If you want to navigate at the same time:

setRouteParamsAndNavigate(router: Router, params: any): void {
  const segments = router.url.split('/');
  for (const p in params)  {
    segments[params[p].position] = params[p].value;
  }
  router.navigateByUrl(segments.join('/')).then(() => {});
}
nash
  • 3,020
  • 6
  • 38
  • 53
1

To make @khush answer more complete and fix the issue requiring id to be unique, I did this adjustment:

id: string;

constructor(private route: ActivatedRoute, private router: Router) {}

ngOnInit() {
    this.route.params.subscribe(params => {
        this.id = params['id'];    
    });
}

navigate() {
    this.router.navigateByUrl(this.router.url.replace(`/${this.id}/`, `/${newId}/`));
}
Sinandro
  • 2,426
  • 3
  • 21
  • 36
0

Would this help?

export class MyComponent {

  constructor(private router: Router, private route: ActivatedRoute){}

  public navigate(){
    const projectId = getNewProjectId(route.snapshot.params['projectId']);
    this.router.navigate([
      projectId, 
      this.router.url.substring(this.router.url.indexOf('/') + 1, this.router.url.length)
    ]);
  }
}

In case you need more granular control (basically, you have no clue what the current URL should look like), try traversing the route tree, processing the route config paths. You can find the :projectId configuration in there and based on where you are in the tree, you can understand your router.url structure.

let route = activatedRoute.snapshot;
while (route) {
  if (route.routeConfig && route.routeConfig.path) {
    // Do something with the path
  }
  route = route.parent;
}

Hope this helps a little :-)

Heehaaw
  • 2,677
  • 17
  • 27
0

In the corresponding component(i.e in .ts file) you need to add

import { Subscription } from 'rxjs/Subscription';

Uder into your @component use following

myVariable: {projectId: string, userId: string};
paramsSubscription: Subscription;


ngOnInit(){
this.myVariable = {
   projectId: this.route.snapshot.params['projectId'],
 // userId: this.route.snapshot.params['userId']
};
this.paramsSubscription = this.route.params
  .subscribe(
    (params: Params) => {
      this.myVariable.projectId = params['projectId'];
    //  this.myVariable.userId = params['userId'];
    }
  );
}

and on which method you are interested to change existing route.let's say you want to change route from following methods

changeRoute(): void{
   this.router.navigate(['/curentUrl',this.project_id_2, 'users/user_id_1/profile']);
}

hope this may helps you

Prasanna
  • 1,752
  • 1
  • 15
  • 27
0

Since I also spent hours in investigating this question, I also want to share my solution.

I added a custom data item to the routes that should be able to switch between accountNames (projectIds in your case):

const routes: Routes = [
 {
   path: 'expenses/:accountName',
   component: ExpenseListComponent,
   data: { changeAccountUrl: ['expenses', ':accountName'] },
 }
];

This way it's easy for any component to check the activatedRoute's data for the presence of that item. If it's present, you can generate a route with it.

One more benefit is that you have more control about the generated routes.

Tho Mai
  • 835
  • 5
  • 25
-1

This is one way you can do it. Get the url, get the current params (because it sounds like you don't know what they are), if you have both projectid and userid then you route to the one with both. If the url ends in 'o' then you are on the /info route and if it ends in 's' then it is the /users route.

constructor(private activatedRoute: ActivatedRoute) {}

replacePrarm(projectId) {
  // get current url
  const url = this.router.url;

  // get current params
  this.activatedRoute.params.subscribe((params: Params) => {
       if(params['projectId'] && params['userId']) {
          router.navigate(projectId, 'users', params['userId'], 'profile'], {relativeTo: route});
       } else if (url[url.length - 1] === 'o') {
          router.navigate(projectId, 'info'], {relativeTo: route});
       } else if (url[url.length - 1] === 's') {
          router.navigate(projectId, 'users'], {relativeTo: route});
       }
  });
}

This is assuming you have no idea what route you are on, but in all reality you should have some idea if you are in users, info, or the profile. Otherwise you're using one component for three very different pages.

rhavelka
  • 2,283
  • 3
  • 22
  • 36