Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 Get router params outside of router-outlet

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...

like image 548
Dan Avatar asked May 10 '16 17:05

Dan


4 Answers

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);
    }
  });
}
...
like image 99
Oleg Hein Avatar answered Sep 19 '22 13:09

Oleg Hein


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...

like image 20
David Pine Avatar answered Sep 18 '22 13:09

David Pine


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.

like image 25
Misi Avatar answered Sep 19 '22 13:09

Misi


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

like image 23
Günter Zöchbauer Avatar answered Sep 18 '22 13:09

Günter Zöchbauer