5

I'm working my way through the Angular 2 documentation on routing. I have a sample application which shows two components which are routed and hooked up to navigation links. Here is a Plunker demonstrating the behaviour. It's built using Angular 2 in Typescript.

Here is the main 'app.component.ts' code:

import {Component} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';

import {CrisisCenterComponent} from './crisis-center/crisis-center.component';
import {HeroListComponent}     from './heroes/hero-list.component';
import {HeroDetailComponent}   from './heroes/hero-detail.component';

import {DialogService}         from './dialog.service';
import {HeroService}           from './heroes/hero.service';

@Component({
  selector: 'my-app',
  template: `
    <h1 class="title">Component Router</h1>
    <nav>
      <a [routerLink]="['CrisisCenter']">Crisis Center</a>
      <a [routerLink]="['Heroes']">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
  `,
  providers:  [DialogService, HeroService],
  directives: [ROUTER_DIRECTIVES]
})

@RouteConfig([

  { // Crisis Center child route
    path: '/crisis-center/...',
    name: 'CrisisCenter',
    component: CrisisCenterComponent,
    useAsDefault: true
  },

  {path: '/heroes',   name: 'Heroes',     component: HeroListComponent},
  {path: '/hero/:id', name: 'HeroDetail', component: HeroDetailComponent},
  {path: '/disaster', name: 'Asteroid', redirectTo: ['CrisisCenter', 'CrisisDetail', {id:3}]}
])

export class AppComponent { }

What I'm looking to do is replace the main (<h1>) heading 'Component Router' with the name of the 'view' that's being rendered. So when you click 'Crisis Center' the heading should say 'Crisis Center'; when you click 'Heroes' the heading should say 'Heroes'.

I don't want to move the heading into the child component templates - I want to keep it at the top of the HTML page and preferably only in one template.

Can anyone suggest the best (most Angular-docs-like) way to accomplish this?

Many thanks.

Dan
  • 5,836
  • 22
  • 86
  • 140

1 Answers1

12

This is how I do it:

-- Firstly, create a service that will be responsible for sharing the route names between components. And create a new Subject "subjects work as an Observable and Observer at the same time"

import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Rx';

@Injectable()
export class RouteNames{
  public name = new Subject();
}

-- Secondly, provide your service at the application level by injecting it on bootstrap:

import {RouteNames} from './route-names.service';
bootstrap(AppComponent, [ROUTER_PROVIDERS,RouteNames]);

Now, all you need to do is inject your service into app.component and into every component that will change the route name.

app.component: subscribe to RouteNames.name subject and update the variable routeName on next.

import {RouteNames} from './route-names.service';
@Component({
  selector: 'my-app',
  template: `
    <h1 class="title">{{routeName}}</h1>
    <nav>
      <a [routerLink]="['CrisisCenter']">Crisis Center</a>
      <a [routerLink]="['Heroes']">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
  `,
   ...
})
@RouteConfig([
   ...
])
export class AppComponent {
  routeName = "Component Router";
  constructor(private _routeNames:RouteNames){
    this._routeNames.name.subscribe(n => this.routeName = n);
  }
}

Now, on CrisisCenter, Inject the RouteNames service and call next on RouteNames.name:

import {RouteNames} from '../route-names.service';

@Component({
  ...
})
@RouteConfig([
  ...
])
export class CrisisCenterComponent {
  constructor(private _routeNames:RouteNames){
    _routeNames.name.next('Crisis Center');
  }
}

And that is it, Here is working plunker

Abdulrahman Alsoghayer
  • 16,462
  • 7
  • 51
  • 56
  • Fantastic, thanks. `Subject` looks handy! For completeness, I've updated the [Plunker](http://plnkr.co/edit/ecqQrlwZ6Cj4tTG1pUfm?p=preview) with the updates on the 'Heroes' component too. – Dan Mar 13 '16 at 21:46
  • 1
    @Dan Yup, I use subjects a lot on my services for communications. I added your plunker edits to the one on my answer. – Abdulrahman Alsoghayer Mar 13 '16 at 21:55
  • Awesome answer! Helped me out loads, thanks! – Joseph Woodward Mar 17 '16 at 15:37
  • This is awesome +1 Any idea if it's possible to take this a step further and do something similar to inject a component (in place of a string) into the header? – mutex Apr 06 '16 at 02:16
  • Thanks @mutex Do you mean something like [this](http://plnkr.co/edit/RrIr6AsjK2NgeDrnkYzG?p=preview)? I only changed the file `app.component.ts` – Abdulrahman Alsoghayer Apr 06 '16 at 03:47
  • 1
    @mutex, or maybe something like [this](http://plnkr.co/edit/n1kiQRjDZaFHXYOV44pw?p=preview) ?, instead of a string, the subject takes a `{component:Type,routeName:string}` object. The `AppComponent` injects the new header component,assigns the `routeName` to it and removes the old component if one exists. – Abdulrahman Alsoghayer Apr 06 '16 at 04:14
  • @Abdulrahman Yes! You legend :) that second example is exactly the sort of thing I wanted to do. Looks like I have some reading to do on DynamicComponentLoader – mutex Apr 06 '16 at 05:16
  • @mutex glad I could be of help. Here is the [DynamicComponentLoader Docs](https://angular.io/docs/ts/latest/api/core/DynamicComponentLoader-class.html). It's very straight forward. Enjoy :) – Abdulrahman Alsoghayer Apr 06 '16 at 05:24