5

Using the @angular/router version 3.0.0-beta.2 I would like to build an application which allows my users to browse a filesystem.

The following illustrates the types of URL I would like to support:

http://myapp/browse                   <--- Browse without parameters, lists root dir
http://myapp/browse/animals           <--- Browse "animals" subdirectory
http://myapp/browse/animals/mammals   <--- Browse "animals/mammals" subdirectory

I am struggling to come up with a mechanism to configure this using RouterConfig.


Path-style Parameters

{ path: 'browse/:path', component: BrowserComponent }

This RouterConfig only supports a single subdirectory, i.e. browse/animals. However, if I attempt to browse to the second-level of subdirectory /browse/animals/mammals I get the following error:

Error: Cannot match any routes: '/browse/animals/mammals'

I understand that the default behaviour of a parameter in the router (in this case :path) is to "break" on the first forward slash, and assume the following token is a sub-path.

How can I configure the router to allow the :path parameter to accept forward slashes / "gobble" the entire remaining URL as the parameter?


Optional Parameter

How can I deal with the use-case where the user wishes to browse the root directory, i.e. the URL /browse. This will not match the router configuration described above, as the :path parameter is not present.

I have attempted to work around this using two distinctly configured router paths:

{ path: 'browse', component: BrowserComponent },
{ path: 'browse/:path', component: BrowserComponent }

However, this is causing me problems with routerLinkActive. I have a main menu with a link to the browser, that looks like this:

<a [routerLink]="['/browse']" [routerLinkActive]="['active']">...</a>

I would like this main menu link to have the active class if the user has browsed to the root /browse or to some subdirectory /browse/animals/.

However, given the /browse/animals/ URL is not a child of the /browse route, it does not become active.

I cannot make the :path parameter route a child of the /browse root, as there is no need for a nested view, and it results in:

Cannot find primary outlet to load BrowserComponent
2
  • Any reason you don't use version beta.2 of the router? I wouldn't expect this to work but several other issues were fixed. Commented Jul 21, 2016 at 10:58
  • @GünterZöchbauer No, there's no good reason I'm not using that version of the router. I'll bump forward my dependency. Commented Jul 21, 2016 at 11:03

5 Answers 5

7

Building on the ** suggestion from @DaSch, it is indeed possible to use the wildcard route:

{ path: '/browse', component: BrowserComponent, children: [ { path: '**' } ] }

There is no need to have a nested router-outlet within the BrowserComponent as there is no component field specified on the child path.

As desired, the BrowserComponent will now be routed for all the described URLs:

http://myapp/browse
http://myapp/browse/animals
http://myapp/browse/animals/mammals

The challenge now is to obtain the par tof the URL which represents the path that the browser has navigated to, and subscribe to changes as the user navigates.

I have prototyped this using the Router:

router.events.filter(e => e instanceof NavigationEnd)
  .forEach((event: NavigationEnd) => {

    // The root route belongs to "/browse"
    let parentRoot: ActivatedRoute = router.routerState.root.firstChild;
    if (parentRoot.snapshot.url.map(p => p.path).join("/") == "/browse") {

      let path: string = "";

      // Child route is optional, in case the user has browsed to just "/browse"
      let childRoute: ActivatedRoute = parentRoot.firstChild;
      if (childRoute) {
        path = childRoute.snapshot.url.map(p => p.path).join("/");
        console.log("New browser path is ", path);
      }

      this.loadPath(path);
    }
  }
);
Sign up to request clarification or add additional context in comments.

4 Comments

Good to know, nice!
@jwa hi, I am using your code sample. It's really perfect. Thank you. I want to ask you a question about it. I use this method in my shared component. And I use it in multiple components as you guess. Like: /browse, /deleted, /shared etc. When I click another route (using shared component), this.router.events.observers length is increasing. When I click completely different route (/foo which isn't have shared component), It resets itself (normally this.router.events.observers length is 17). How can I prevent increasing observer array's length when I go another route which has shared component?
@jwa ok I solved it with subscription. Could you please check if it's true? this.routerSubscription = router.events.subscribe(event => { if (event instanceof NavigationEnd) { console.log('bbb'); } }); ngOnDestroy() { this.routerSubscription.unsubscribe(); }
Just in case it helps anyone: in Angular 4.1.x, I used component in the wildcard child rather than the parent: {path: 'browse', children: [{path: '**', component: BrowserComponent}]}. I liked your version slightly better, but after changing it I ran into "Invalid configuration of route 'browse/**'. One of the following must be provided: component, redirectTo, children or loadChildren". As my previous version worked just fine for me too, I reverted my change and did not investigate...
5
+50

I haven't tried this, but from the documentation, I would try to use children with a wildcard selector.

{ path: 'browse', 
  component: ...,
  children: [
     path: '**',
     component: ...
]},

It's just guess and I would have to check how to retrieve the path parameters to get the right content for the given path.

3 Comments

That won't work. First you dont have an router-outlet in this component and if you would add one, the component would be rendered again in our component.
Well, that you might need two different components. That's details It's just about using wildcards in children path.
@mxii - Having prototyped this I can confirm that you would NOT need a new outlet. The additional outlet is only required if a component is specified.
0

You could try it this way..

Just using your first route: { path: 'browse', component: BrowserComponent }

And for the folder names using so called optional routes.

The link should have this syntax here:

<a [routerLink]="['/browse', ['f1', 'f2', 'f3', 'f4'] ]">--link name--</a>

or like this:

<a [routerLink]="['/browse', { folders: ['f1', 'f2', 'f3', 'f4'] } ]">--link name--</a>

Inject private _route: ActivatedRoute to your Component's constructor and subscribe to the params:

this._route.params.subscribe(
      val => console.log(val),
      err => console.log(err));

The first will print this: Object {0: "f1", 1: "f2", 2: "f3", 3: "f4"} and the second will print this: Object {folders: "f1,f2,f3,f4"}

Or maybe you will get another idea to add your folders using this optional parameters.. gl! :)

Comments

-1

You can configure the router dynamically like explained in https://stackoverflow.com/a/38096260/217408

router.resetConfig([
 { path: 'team/:id', component: TeamCmp, children: [
   { path: 'simple', component: SimpleCmp },
   { path: 'user/:name', component: UserCmp }
 ] }
]);

https://github.com/angular/angular/issues/11437#issuecomment-245995186 provides an RC.6 Plunker

2 Comments

In my understanding that's not the real problem. If I understand him right, the route itself is just "/browse" and he has only this component, but he wants to put the arguments in this style URL/browse/folder1/folder2/folder3/..../folderN. and this is not possible (if i am right) ..
But he can reconfigure a router to support more parameters or add child routes to get this behavior. I think using router.resetConfig is a valid approach.
-1

Can useful, for me good worked router.resetConfig() with new route rules in custom logic of my app

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.