12

I have a dashboard application which consists of a treeview component (which lists various content nodes) and a dashboard-edit component which renders some editable content depending on which branch of the tree is selected.

e.g. The tree is like this:

- Football
- - Premier League
- - - Arsenal
- - - Chelsea
- - - ...etc
- - Championship
- - - Derby
- - - ...etc

You click 'Arsenal' in the tree and it renders some content for that team in an editable panel on the page.

The component which renders the sub-components is like this:

@Component({
    selector: 'my-dashboard',
    template: `
        <div class="tree-panel-container">
            <div class="tree-panel-content">
                <content-tree [startNodeId]="startNodeIdContent"></content-tree>
            </div>
        </div>
        <router-outlet></router-outlet>
    `,
    directives: [
        ContentTreeComponent, 
        ContentDashboardComponent, 
        RouterOutlet
    ],
    providers: [
        HTTP_PROVIDERS
    ]
})

The editable content is rendered in a router-outlet so that each editable piece of content has its own distinct URL e.g. example.com/content/edit/123 where 123 is the id of the Arsenal content, for example.

This all works fine.

However, what I want to do is be able to access the id route parameter in the content-tree component. Currently, I'm pretty sure the code I have in that component should work:

import {Component, Input, OnInit}   from '@angular/core';
import {Router, RouteParams}        from '@angular/router-deprecated';

import {ContentNode}                from './content-node';
import {ContentService}             from '../services/content.service';


@Component({
    selector: 'content-tree',
    directives: [ContentTreeComponent],
    template: `
        <ol class="tree">
            <li *ngFor="let contentNode of contentNodes" class="tree__branch" [ngClass]="{'tree__branch--has-children': contentNode.HasChildren}">
                <a *ngIf="contentNode.HasChildren" (click)="contentNode.toggle=!contentNode.toggle" class="tree__branch__toggle">
                    {{ !!contentNode.toggle ? '-' : '+' }}
                </a> 
                <a class="tree__branch__link" (click)="onSelect(contentNode)">{{ contentNode.Name }}</a>
                <content-tree *ngIf="contentNode.toggle" [startNodeId]="contentNode.Id"></content-tree>
            </li>
        </ol>
        <div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
    `
})
export class ContentTreeComponent implements OnInit {

    constructor(
        private _contentService: ContentService,
        private _router: Router,
        private _routeParams: RouteParams
    ) { }

    errorMessage: string;

    @Input('startNodeId')
    private _startNodeId: number;

    contentNodes: ContentNode[];

    ngOnInit() { 
        let nodeId = +this._routeParams.get('id');
        console.log('nodeId = ' + nodeId);
        this.getContentNodes();
    }

    onSelect(contentNode: ContentNode) {
        this._router.navigate( ['ContentEdit', { id: contentNode.Id }]  );
    }

    getContentNodes() {
        this._contentService.getContentNodes(this._startNodeId)
            .subscribe(
                contentNodes => this.contentNodes = contentNodes,
                error =>  this.errorMessage = <any>error
            );
    }
}

But the nodeId variable in the ngOnInit method is always returned as 0.

Questions: Is it only possible to access route params in a component rendered by a router-outlet? If so, then is the best method to deal with this to create a second (named, because there will now be 2) router-outlet? If not, then what am I doing wrong?

Many thanks.

EDIT:

A working (and very ugly ;)) Plnkr has now been generated to show the basics of the app: http://plnkr.co/edit/W3PVk3Ss5Wq59IbnLjaK?p=preview. See comments for what is supposed to happen...

Dan
  • 5,836
  • 22
  • 86
  • 140

5 Answers5

16

Get the active route from outside a component in angular 2.1.0 and Router 3.1.0

I found a nice way to get all params, queryParmas, segments and fragments from the displayed route from anywhere inside your App. Just add this Code to any Component where you need it, or create a Service that can be injected throughout your App.

import { Router, NavigationEnd } from "@angular/router";
import { Component, OnInit } from '@angular/core';

...

export class MyComponentOrService implements OnInit {

constructor(private router: Router) {}

ngOnInit() {

  /* this subscription will fire always when the url changes */
  this.router.events.subscribe(val=> {

    /* the router will fire multiple events */
    /* we only want to react if it's the final active route */
    if (val instanceof NavigationEnd) {

     /* the variable curUrlTree holds all params, queryParams, segments and fragments from the current (active) route */
     let curUrlTree = this.router.parseUrl(this.router.url);
     console.info(curUrlTree);
    }
  });
}
...
Oleg Hein
  • 409
  • 5
  • 6
  • 2
    Filter the events before subscription -------> this.router.events.filter(event => event instanceof NavigationEnd).subscribe(event => console.log(event.url)); – Amsakanna Jun 08 '17 at 18:16
  • This did work for me, but it seems so clunky; I had to dot into the curUrlTree a good bit, and then the parameters are not named and had to be referenced by index. The answer provided by Misi, below, seems to work well and references the params by name. (giving a point to both) – Dan Overlander Jun 20 '19 at 12:57
5

This solution worked for me: complete example.

constructor(
  private readonly router: Router,
  private readonly rootRoute: ActivatedRoute,
){
  router.events.pipe(
    filter(e => e instanceof NavigationEnd),
    map(e => this.getParams(this.rootRoute))
  ).subscribe(params => {
   //
  });
}

private getParams(route: ActivatedRoute): Params {
  // route param names (eg /a/:personId) must be ditinct within
  // a route otherwise they'll be overwritten
  let params = route.snapshot.params
  params = { ...route.snapshot.queryParams, ...params}
  if(route.children){
    for(let r of route.children){
      params = {...this.getParams(r), ...params};        
    }
  }
  return params;
}

Credits to Toxicable.

Misi
  • 748
  • 5
  • 21
  • 46
4

Is it only possible to access route params in a component rendered by a router-outlet?

Yes, the <router-outlet></router-outlet> tells Angular2 to treat the containing component as a "routing" component. Therefore you cannot get a RouteParams instance injected into the class as it wasn't instantiated via the routing directive.

If not, then what am I doing wrong?

I wouldn't say you're doing anything wrong, you simply had a misconception on how it was designed. I too has this initial misconception. I found this Angular2 article to be a great source for understanding how to pass data around and how to communicate betwixt parent and child components.


In your specific case I'd suggest removing the RouteParams from the constructor of the ContentTreeComponent as it will only be available if rendered from a "routing" component.
export class ContentTreeComponent implements OnInit {

    constructor(
        private _contentService: ContentService,
        private _router: Router
    ) { }

    // Omitted for brevity...
}

Then in order to get the id, you'd probably have to share a bit more of your top-level code so that I can see where it is coming from...

David Pine
  • 23,787
  • 10
  • 79
  • 107
  • Thanks David - the docs on component interaction are really useful. That said I'd be very grateful for a steer on the direction to take with my app as I lack the experience to pick out the right approach. I've uploaded a chunk of code here: http://plnkr.co/edit/l0UFk2pkq2ZjrKOEB7vm which doesn't run (I think Plnkr is having issues with the deprecated router module). The goal is to get the `id` from the route attribute into the content-tree.component. Any thoughts on this would be massively appreciated! – Dan May 10 '16 at 21:27
  • 1
    I updated that a little and got it a lot closer, there is just one error now -- http://plnkr.co/edit/p8hJMapaLLs1A9J2EEHP?p=preview. Something about an invalid provider, hidden by the `undefined.toString` attempt. – David Pine May 11 '16 at 01:58
  • At last! I've managed to get the Plnkr working, using the new release-candidate router (rather than the deprecated one): http://plnkr.co/edit/W3PVk3Ss5Wq59IbnLjaK?p=preview. So to see what I mean, click 'content' in the red panel on the left, then click one of the tree nodes in the purple panel. You'll see the content on the left changes to show the selected tree item and the URL has an id appended to it. What I want to do is access that id value in the content tree component. Any ideas most welcome! :) – Dan May 11 '16 at 21:19
  • (Can't seem to edit that comment, but it should read "You'll see the content on the right" rather than "You'll see the content on the left".) – Dan May 12 '16 at 07:27
  • 1
    My current line of thinking is that a service might be necessary here. So the service is set up to hold the id from the URL and the content-edit component updates this value on init. Sensible? Crazy? Better solution? Thanks! – Dan May 14 '16 at 15:43
  • 1
    I think a micro-service is a great way to approach this! Have one be the consumer and one be the producer, problem solved. https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service – David Pine May 14 '16 at 17:45
  • Surprises me that it is a so hard to find solution, its not a specific scenario that you have @Dan, i'm having this same issue too, 'cause i have a component that sets the page title, wich is dynamic (i can´t put the title in the route configuration), and my component sits outside *router-outlet*... Did u solve that? – Richard Lee Feb 07 '17 at 17:19
2

In the new router (>= RC.0 <=RC.2) this would be

  import 'rxjs/add/operator/first';
  ...

  constructor(private router:Router, private routeSerializer:RouterUrlSerializer, private location:Location) {
    router.changes.first().subscribe(() => {

    let urlTree = this.routeSerializer.parse(location.path());
      console.log('id', urlTree.children(urlTree.children(urlTree.root)[0])[0].segment);
    });
  }

See also Angular 2 RC1: Get parameters from the initial URL used

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks Günter. Am I right in saying this will only work on a component that's rendered via a router-outlet? If so I'll need to employ the other way of doing it you mention in the other thread (http://stackoverflow.com/questions/37218706/angular-2-rc1-get-parameters-from-the-initial-url-used/37230716#37230716) because the component I'm seeking to get the param into is outside of the router-outlet, so I'd need to use the constructor of `AppComponent` instead? – Dan May 14 '16 at 20:37
  • Sorry, I misinterpreted your question. Yes this is exactly the opposite of what you need. The approach you mentioned from the other answer should work everywhere where you can inject the `Router`. I updated the answer. – Günter Zöchbauer May 14 '16 at 20:41
  • 1
    Awesome! So in my case I just needed to add this to the content.component.ts rather than the app.component.ts (as app.component.ts was too high up the routing hierarchy. Thanks so much, this has been a massive blocker! – Dan May 14 '16 at 20:53
  • There doesn't seem to be a 'Location' class in `@angular/router`. Is there another import needed? – Jason Goemaat May 25 '16 at 06:18
  • Location was moved `import {Location} from '@angular/common';` See also http://stackoverflow.com/questions/36861628/location-and-hashlocationstrategy-stopped-working-in-beta-16 – Günter Zöchbauer May 25 '16 at 06:20
  • 3
    RouterUrlSerializer was removed somewhere in the [new beta router](http://victorsavkin.com/post/145672529346/angular-router) I'm getting the URL from Router and parsing it by hand.. `parseInt(this.router.url.split("/")[2])` – golfadas Jul 07 '16 at 09:31
  • how to import RouterUrlSerializer ? – Reza Jul 25 '19 at 19:35
0

Because of the solutions found in this post weren't able to solve my problem, I've just added an other solution that could currently fix or help you to deal with this kind of issue on an other post with a similar question: Angular 2: How do I get params of a route from outside of a router-outlet

schankam
  • 10,778
  • 2
  • 15
  • 26