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
jwa
  • 3,239
  • 2
  • 23
  • 54
  • 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. – Günter Zöchbauer Jul 21 '16 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. – jwa Jul 21 '16 at 11:03

5 Answers5

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);
    }
  }
);
Arjan
  • 22,808
  • 11
  • 61
  • 71
jwa
  • 3,239
  • 2
  • 23
  • 54
  • Good to know, nice! – slaesh Aug 19 '16 at 11:35
  • @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? – Sam Mar 01 '17 at 12:18
  • @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(); }` – Sam Mar 01 '17 at 13:18
  • 3
    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... – Arjan May 16 '17 at 20:38
5

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.

DaSch
  • 931
  • 1
  • 5
  • 20
  • 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. – slaesh Aug 17 '16 at 15:10
  • Well, that you might need two different components. That's details It's just about using wildcards in children path. – DaSch Aug 18 '16 at 08:04
  • 2
    @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. – jwa Aug 19 '16 at 10:40
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! :)

slaesh
  • 16,659
  • 6
  • 50
  • 52
-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

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 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) .. – slaesh Aug 18 '16 at 08:56
  • 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. – Günter Zöchbauer Aug 18 '16 at 08:57
-1

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

Oleg Postoev
  • 182
  • 1
  • 5