56

I have a sub-navigation in my page that displays some subviews below a common main view. I would like to pass an object to the subviews through the <router-outlet> so that I can retrieve the data once in the main component and just share it with my sub components.

Note: If I include the directive <one></one> in the main.html it works but this isn't my desired behavior.

Main View:

<h1>Details</h1>   
<a [router-link]="['./sub1']">One</a> | 
<a [router-link]="['./sub2']">Two</a> | 
<a [router-link]="['./sub3']">Three</a>   
<hr/>  
<router-outlet [data]="maindata"></router-outlet>

Sub View 1:

<h2>{{ data.name }}</h2>
...

Main View:

@Component({
    selector: 'main-detail',
    directives: [ROUTER_DIRECTIVES],
    templateUrl: './main.html'
})
@RouteConfig([
    { path: '/', redirectTo: '/one' },
    { path: '/one', as: 'One', component: OneComponent },
    { path: '/two', as: 'Two', component: TwoComponent },
    { path: '/three', as: 'Three', component: ThreeComponent }
])
export class MainComponent {
    maindata: Object = {name:'jim'};
}

Sub View 1:

@Component({
    selector: 'one',
    directives: [CORE_DIRECTIVES],
    inputs: ['data'],
    templateUrl: './one.html'
})
export class OneComponent {
    @Input() data;
}
Jason Swett
  • 43,526
  • 67
  • 220
  • 351
JRulle
  • 7,448
  • 6
  • 39
  • 61

4 Answers4

21

If it's simple data you can pass them through RouteParams

<a [router-link]="['./sub3'],{name:'jim'}">Three</a>

then in your sub view

@Component({
    selector: 'one',
    directives: [CORE_DIRECTIVES],
    templateUrl: './one.html'
})
export class OneComponent {
    data: any;
  constructor(params: RouteParams){
    this.data = params.get('data');
  }
}

You can also setup the route to always pass params from the component by moving the RouterConfig INSIDE the component (Note, this is not how it's normally done):

export class AppCmp {
  history: string[] = [];
  constructor(public list: PersonalizationList,
              private router_: Router) {
    list.get('histoy', (response) => {
      this.history = response;
    });
    router_.config([
      { path: '/', component: HomeCmp, as: 'Home', data: this.history },
      { path: '/about', component: AboutCmp, as: 'About' }
    ]);
  }
}

Credit to the Source

If you are going to do something more complex I suggest using a service to communicate between routes/components. It's actually the way I prefer to do it.

Sample Service:

import {Injectable} from 'angular2/angular2';

@Injectable()
export class CarsService {
  list1: array<any> = ['a','b','c','d'];
  list2: array<any>;

  constructor() {
    this.list2 = [1,2,3,9,11];
  }
}

How you Inject a service:

export class Cars {
  constructor(cars:CarsService) {
    this.cmpList1 = cars.list1;
    this.cmpList2 = cars.list2;
  }
}

This way you can use the service to communicate regardless of parent/child or other weird restrictions.

Community
  • 1
  • 1
Dennis Smolek
  • 8,480
  • 7
  • 30
  • 39
  • 6
    unfortunately I need to pass more than simple data (an object). I was trying to avoid creating a service that was solely responsible for passing data between two components but it seems I may not have a choice. – JRulle Dec 23 '15 at 13:16
  • 1
    With Angular 2.0.0-beta.8 Injectable can be imported from 'angular2/core' – Des Horsley Mar 12 '16 at 11:20
  • 2
    With the new v3 router is this unfortunately out of date. – marked-down Jul 08 '16 at 22:57
  • 1
    Yes, unfortunately when I had the most time for S.O. The code was still changing daily and I decided I can't go through and constantly update the answers as many of the questions are also out of date. The method for the new router is similar though, and I still recommend using a service. – Dennis Smolek Jul 21 '16 at 14:45
  • 2
    There was a github issue discussion around this topic. As for Anuglar rc-0.5, they decided not allow @Input like param passing on and there is an tutorial about using service for doing this job [Parent and children communicate via a service](https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service) – yeelan Sep 01 '16 at 17:52
  • in my case - angular 2.0 beta 17 - it's `Three`. A route name and parameters are in the same array. – res Jan 09 '17 at 12:30
  • 1
    Service injection has been convenient since day one. It's also a horrible idea. You do not want to weld services onto components which is why the @input decorator exists. – Rick O'Shea Jun 11 '18 at 04:06
16

Looks like syntax has been changed. Below works for me ~Angular4.0.0

HTML (Pass Route Parameters)

<li><a [routerLink]="['/templatecreate',{mode:'New'}]">New Job</a></li>

Component

constructor(private route: ActivatedRoute) { }

ngOnInit() {       
  this.getTemplate();

  this.sub = this.route.params.subscribe(params => { this.id = params['mode'];
  console.log("Routing Mode", this.id);    
  });
}
spottedmahn
  • 14,823
  • 13
  • 108
  • 178
Tanmay
  • 325
  • 1
  • 5
  • 13
1

I think the proper Angular2 way to be passing data is via Dependency Injections (by using a Service) otherwise the user will be able to see the data you're passing in the browser's URL.

Also, using a Service will allow for "Separation of Concerns", meaning that Component A shouldn't be dependent on Component B.

Dependency Injection Links:

1) https://angular.io/guide/dependency-injection

2) https://angular.io/guide/dependency-injection-in-action

3) https://www.youtube.com/watch?v=MJrw43GP2u0

Gene
  • 10,819
  • 1
  • 66
  • 58
  • 4
    Components should take @inputs rather than welding them inextricably to services. I don't want to have to create a service for no other reason than to use your component. We need a way to pass inputs to components when routing. – Rick O'Shea Jun 11 '18 at 04:18
  • @RickO'Shea 2019 and yet not imporved, still no input I m new to angular My first thought when I tried to use routing was to use inputs, what a shame – Mohammed Housseyn Taleb Jun 07 '19 at 00:54
1

We have a big angular project (just starting with Angular, so solutions are as good as our understanding :-) ).

The shell component can call any of the 4 (route-based) "action" modules - where each module has its own service (but no component view) and can call any of the 6 (route-based) shared Components. The shared components are shared between all 4 services, so they cannot have any logic specific to the calling module.

We are using a service resolver ActionModuleServiceResolver which DIs all 4 action services. Based on state (RouterStateSnapshot) URL, we return the appropriate service.

@Injectable()
export class ActionModuleServiceResolver implements Resolve<ActionModuleService> {

  constructor(private _mod1: ModOneService,
    private _mod2: ModTwoService, private _mod3: ModThreeService,private _mod4: ModFourService) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): ActionModuleService {
    if(state.url.includes(`/${ActionModuleType.ModOne}/`))
      return this._mod1;
    else if(state.url.includes(`/${ActionModuleType.ModTwo}/`))
      return this._mod2;
....
else
  return null;
  }
}

Each Action Module's Routing module routes to the shared component like this:

    const routes: Routes = [
  {
    path: 'sharedMod1', component: SharedModOneComponent, data: {
      title: `ModOne_SharedModOne`,
      routeName: 'sharedMod1'
    }, resolve: { actionModule: ActionModuleServiceResolver }
  },

Next, each SharedModule gets the activated route via DI and gets the calling service:

//SharedModOne.component.ts
constructor(protected route: ActivatedRoute) {}

  ngOnInit() {
    this.actionModSvc= this.route.snapshot.data['actionModule'];
    this.actionModSvc.getDesignFile(this.route);
  }

Hope this helps someone, also if this can be improved, please let me know.

Thanks,

RDV

RDV
  • 957
  • 13
  • 28