Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular: resolve all route segments configuration by URL

Given a URL, e.g. /path/path2/path3/123;sdf=df and a routes configuration:

{ path: 'path', data: { g: 'h' }, children: [
  { path: 'path2', data: { c: 'd' }, children: [
    { path: 'something', component: TestpageComponent, data: { a: 'b' } },
    { path: 'path3/create', component: TestpageComponent, data: { b: 'c' } },
    { path: 'path3/:id', component: TestpageComponent, data: { b: 'c' } },
  ] }
] },

What I need is to find an actual configuration for all the segments in order to get a result set of all data parameters on all levels (each segment of the url).

I can split the URL in the segments by e.g.

console.log(this.router.parseUrl(url));

or to be more precise

console.log(this.router.parseUrl(url).root.children['primary'].segments);

which will return

[{"path":"path","parameters":{}},{"path":"path2","parameters":{}},{"path":"and","parameters":{}},{"path":"123","parameters":{"sdf":"df"}}]

So, splitting the URL into segments is not a problem. However I need to get a configuration for each segment.

I can get the actual configuration for all the routes with

console.log(this.router.config);

I can go with the segments through the configuration tree and find out the branch I need, but it can cause troubles e.g. while resolving :id against create segment. So, I would like to use the router's way of resolving the configuration that if the inner router implementation changes I would not need to change mine.

Example: imagine I have a 50% of the URLs protected by some guard (e.g. only logged in users could go there) and other 50% is not protected (displayed for everybody). So, on the navigation menu level (outside of the router-outlet) I want to display only the links which are relevant for the current user, so I need to know which routes are protected by the guard. Guards / data / whatever is just a particular case of the problem.

Please do not stick to the example, I am searching for a common way to get the configuration set for a particular URL.

Is this anyhow possible?

like image 433
smnbbrv Avatar asked Mar 31 '17 14:03

smnbbrv


People also ask

Can not match any routes URL segment?

This generally occurs when there is a mismatch in the routes specified, or a component which is not present in our routing configuration, or if there is the use of multiple routes in our angular application which is not properly configured.

Which Angular package is used to route to URL?

RouterModule.forRoot() link The method is called forRoot() because you configure the router at the application's root level. The forRoot() method supplies the service providers and directives needed for routing, and performs the initial navigation based on the current browser URL.

What does pathMatch Full mean?

pathMatch: 'full' means, that the whole URL path needs to match and is consumed by the route matching algorithm.

What does ActivatedRoute do in Angular?

ActivatedRoutelink. Provides access to information about a route associated with a component that is loaded in an outlet.


2 Answers

With version 2.x you can use the recognize function the router internally uses to match an url to a RouteStateSnapshot.

import { recognize } from '@angular/router/src/recognize';

recognize(null, this.router.config, this.router.parseUrl(url), url).subscribe(route => {
    var routeSnapshot = route.root.firstChild;

    // Go to all children to get actual route.
    // If you use routes with children you should have
    // the full tree here
    var childSnapshot = routeSnapshot.firstChild;
    while (childSnapshot != null) {
        childSnapshot = routeSnapshot.firstChild;
        if (childSnapshot != null) {
            routeSnapshot = childSnapshot;
        }
    }

    let routeData = routeSnapshot.data || {};

    // routeData['a']...
});

Sadly this seems to no longer work with version 4.x. For version 4.x i've created a pull request to add a new method to the Router to expose the recognize function.

https://github.com/angular/angular/issues/15826

Another side note. it makes sense to also handle errors of the promise (either of the recognize or the new router method), by adding .catch((rej) => { // some code if it fails to resolve route }). It can happen that it fails to resolve the url->routestatesnapshot. Either because the url is wrong or you use async routes via LoadChildren and the routes are not yet loaded. Breadcrumbs work great using this even with async routes. If you use it to check permissions, for example via a directive, you need to watch out if you use async routes.

like image 110
smurfy Avatar answered Oct 21 '22 06:10

smurfy


There is another way to get the data config for routes. It involves knowing what the key property in the data object.

In your component you can get the data for that exact route with

this.route.snapshot.data['key']  

You could get the parent's data with

this.route.snapshot.parent.data['key']

Or even the grandparent's data with

this.route.snapshot.parent.parent.data['key']

Since you are using different keys on all your routes, that makes it more difficult. In that case, you'd need to get a list of the keys and iterate over that:

let keys = Object.keys(this.route.snapshot.data);
keys.map(x => {
    console.log('key', x);
    console.log('data', this.route.snapshot.data[x]);
}

Also, if you know your url segments you can even match on that to get the config for a particular path:

let configForRoute = this.router.config.find(x => x.path === this. route.snapshot.url.toString())

let dataForSegment = this.router.config.find(x => x.path === 'somePath').data['key']

A crude solution to get all the data you need would look something like this:

obj = {};
getData(routeData){
  let keys = Object.keys(routeData);
  keys.map(x => this.obj[x] = routeData[x])
}

this.getData(this.route.snapshot.parent.parent.data);
this.getData(this.route.snapshot.parent.data);
this.getData(this.route.snapshot.data);
console.log(obj);

Hopefully this helps get you on the right track to getting the data you need.

EDIT

I may have misunderstood the original question.

Since the addition of:

Why would I need that? Imagine I have a 50% of the URLs protected by some guard (e.g. only logged in users could go there) and other 50% is not protected (displayed for everybody). So, on the navigation menu level (outside of the router-outlet) I want to display only the links which are relevant for the current user, so I need to know which routes are protected by the guard. Guards / data / whatever is just a particular case of the problem.

The way I solved this problem of hiding links either unauthenticated users could see or hiding links unauthorized users could see is to create a function on my authentication service that can either check whether a user is authenticated or authorized. It is the same function that is also used by my guards on my routes.

For example, I may have a CheckPermissions guard on my routes with the route data data: { permissions:[permission1, permission2] } Behind the scenes this guard uses a function on the auth service to check whether the user has permission using the listed permissions in the data field. I can also use that same function from the auth service to hide links in my main menu with *ngIf="authSvc.CheckPermission(permission1) etc. This way I'm not having to duplicate the logic anywhere. Ultimately the API endpoints should be protected server side as well.

As for being able to give the router a url and get back the route that it matches without navigating to it, I can't find a documented way to do that. Hope this is at least a little bit of help.

like image 3
Tyler Jennings Avatar answered Oct 21 '22 04:10

Tyler Jennings