56

In some of my Angular route guards, I want to set up the "next" path, to redirect to after successful login.

So, the ordinary guard canActivate function signature looks like this:

public canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | boolean {
  // ...blah
  return true;
}

The route parameter is an instance of ActivatedRouteSnapshot.

Previously to get the "next" URL I was just getting it from the route.url. This works just fine, as long as there are no children routes.

My example URL is /search/advanced?query_hash=1221d3b57f5616ee16ce70fdc78907ab, where advanced is a child route of a search.

Child routes can be found in route.children, but iterating over these children (especially there might be multiple levels) and combining the URL this way seems awkward and ugly.

What I'm interested in is contained in route._routerState.url property (being a string, on the bottom of the image below), but it's a "private" variable.

Am I missing something? How can one elegantly get the full (with children paths) URL from the ActivatedRouteSnapshot? Angular version is 5.1.

enter image description here

Dale K
  • 25,246
  • 15
  • 42
  • 71
Radosław Roszkowiak
  • 6,381
  • 3
  • 15
  • 27

4 Answers4

57

There's no ready to use function from Angular router to achieve that, so I wrote them:

function getResolvedUrl(route: ActivatedRouteSnapshot): string {
    return route.pathFromRoot
        .map(v => v.url.map(segment => segment.toString()).join('/'))
        .join('/');
}

function getConfiguredUrl(route: ActivatedRouteSnapshot): string {
    return '/' + route.pathFromRoot
        .filter(v => v.routeConfig)
        .map(v => v.routeConfig!.path)
        .join('/');
}

Example output when route is from ProjectComponent:

const routes: Routes = [
    {
        path: 'project', component: ProjectListComponent, children: [
            {path: ':id', component: ProjectComponent}
        ]
    },
];
getResolvedUrl(route) => /project/id1
getConfiguredUrl(route) => /project/:id
Marc J. Schmidt
  • 8,302
  • 4
  • 34
  • 33
  • 2
    @marc-j-schmidt great function. I found that when you have feature modules with child routes `.map` can return segments that are empty strings. Kind of like the first element you get in the array. So the array can look like `['', 'manager', '', 'users']` resulting in a `resolvedUrl` that looks like `/manager//users`. I hacked it with a `.replace('//','/')` to deduplicate for now. Probably a `filter` to prevent empty strings in the first place and prepending the final `resolvedUrl` with a `/` may be the better design choice. Any thoughts? – Doguhan Uluca Nov 14 '19 at 19:46
  • Yes, adding a filter checking for empty paths should be sufficient. – Marc J. Schmidt Nov 14 '19 at 19:59
  • 2
    This has been working great for me for a long time - but now I need to get the URL when there are auxiliary routes. This method just squishes them all together without the parenthesis. Can't believe there isn't a build in way :-/ – Simon_Weaver Nov 19 '19 at 07:46
52

try this to get it from RouterStateSnapshot

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
      console.log(state.url)
      ...
Fateh Mohamed
  • 20,445
  • 5
  • 43
  • 52
  • 26
    This don't anser the question, which stated how to get the URL from 'ActivatedRouteSnapshot'. This interface not only used in `canActivated()` but in many other places for example `shouldReuseRoute(route: ActivatedRouteSnapshot)` there you don't have the 2nd parameter 'state' so how to get the URL from ActivatedRouteSnapshot?? – Gil Epshtain Aug 12 '18 at 12:01
  • 1
    yes, it works for me - window.location.origin + state.url – Dmitry Grinko Aug 03 '20 at 22:27
  • 14
    While this didn't answer the question, it was perfect for what I was looking. Thanks for putting this up! – Mukesh Sai Kumar Aug 26 '20 at 15:28
  • 2
    Since the question asked about `CanActivate.canActivate()`, this is a perfectly good solution to the questions problem. The questions title does not fit the proposed problem. – halllo Feb 21 '22 at 13:33
5

The solution of Marc for his getResolvedUrl does not return query parameters. The function below has added support for queryparameters.

getResolvedUrl(route: ActivatedRouteSnapshot): string {
  let url = route.pathFromRoot.map((v) => v.url.map((segment) => segment.toString()).join('/')).join('/');
  const queryParam = route.queryParamMap;
  if (queryParam.keys.length > 0) {
    url += '?' + queryParam.keys.map(key => queryParam.getAll(key).map(value => key + '=' + value).join('&')).join('&');
  }
  return url;
}
Edwin Stoteler
  • 1,218
  • 1
  • 10
  • 25
4

There already is a ready to use function in Angular to achieve this createUrlTreeFromSnapshot:

Firstly, you can now return just the UrlTree from the canActivate method. And secondly, if you want to compose a URL as string, you can use UrlSerializer API which should e.g. reflect hash vs. path URL strategies:

import { createUrlTreeFromSnapshot, UrlSerializer } from '@angular/router';

// ...

class MyClass {

    constructor(private urlSerializer: UrlSerializer) {}

    getResolvedUrl(route: ActivatedRouteSnapshot) {
        return this.urlSerializer.serialize(
            createUrlTreeFromSnapshot(route.snapshot, ['.'])
        );
    }
}
wimby
  • 41
  • 3
  • I'm not sure when this was added, but it does not appear to be in Angular 12, which I acknowledge is a pretty old version. – NimbusScale Mar 30 '23 at 18:10