6

I have an application which routes would look like this:

/:partner/login
/:partner/tools
/:partner/other

Currently using 2.0.0-rc.5, I can quite simply build routes that look exactly as above. However, a downside of this approach is that each component has to extract the partner parameter on its own. So the login path declaration would look like:

{
    path: ':partner/login',
    component: LoginComponent
}

And the LoginComponent would look like:

export class LoginComponent implements OnInit {
    partner: string;
    constructor(private _route: ActivatedRoute) { }

    ngOnInit() {
        this._route.params.forEach(p => {
            this.partner = p[`partner`];
        });
    }
}

While this is manageable with 3 routes, conceptually, what I really want to say is that I have a PartnerComponent which has child routes login, tools, etc. The routing would look like this:

{ path: ':partner', component: PartnerComponent
    children: [
        { path: 'login', component: LoginComponent},
        { path: 'tools', component: ToolsComponent}
    ]
 }

And using TypeScript's inheritance, I can create a base component for each child like:

export class BaseComponent implements OnInit {
    partner: string;
    constructor(private _route: ActivatedRoute) { }

    ngOnInit() {
        this._route.parent.params.forEach(p => {
            this.partner = p[`partner`];
        });
    }
}

and child components would look like:

export class LoginComponent extends BaseComponent {
    constructor(route: ActivatedRoute) {
        super(route);
    }

    ngOnInit() {
        super.ngOnInit();
    }
}

While the above works, it feels a bit dirty to look up the parent route. I know if I change the hierarchy, I only need to change it in the base, but still

Is there a cleaner way to share information between a parent and its child route components.

If it were a more traditional parent element/child element relationship, we would simply use data binding between the two to pass that information.

Eric Liprandi
  • 5,324
  • 2
  • 49
  • 68
  • Is component data binding out of the question? I don't really see `partner` being a component, but as data. Can the component that owns `partner` pass it to the child (i.e `login`, `tools`, `other) with data binding? – Kevin Le Sep 02 '16 at 22:55
  • @KevinLe AFAIK, the Router is the supported way to parse the URL and extract parameters. And you are correct that for now, the `PartnerComponent` really does not have much else besides a `` tag. However, I can see this evolve in the long run and be more sophisticated. – Eric Liprandi Sep 02 '16 at 23:02
  • Hm, still unsure you need to parse the url itself for `partner`. I posted an answer below, see if it helps you out! You might have already explored it, though. – Kevin Le Sep 02 '16 at 23:17
  • Check out http://stackoverflow.com/a/39031152/1810391 . Instead of passing the parent id, just hardcode the same shareObj id on both side. You can explore method 2 also. – John Siu Sep 03 '16 at 05:20

2 Answers2

4

What you can do is to use a Resolve guard for your parent route, so the data will be loaded for any :partner route.

A Resolve would look like:

import { Injectable }             from '@angular/core';
import { Router, Resolve,
         ActivatedRouteSnapshot } from '@angular/router';

import { Partner } from './partner.model';
import { Api } from './api.service';

@Injectable()
export class PartnerResolve implements Resolve<Partner> {
  constructor(private api: Api,
              private router: Router) {}

  resolve(route: ActivatedRouteSnapshot): Observable<Partner> {
    let id = +route.params['partner'];

    return this.api.getPartner(id);
  }
}

Then, your route would need to include the PartnerResolve so it would not load until the data was requested and it's available:

{
  path: ':partner', component: PartnerComponent,
  resolve: {
    partner: PartnerResolve
  },
  children: [
    { path: 'login', component: LoginComponent},
    { path: 'tools', component: ToolsComponent},
  ]
}

And your PartnerComponent would just get it with:

ngOnInit() {
  this.route.data.forEach((data: { partner: Partner }) => {
    this.partner = data.partner;
  });
}

And your child components would use:

ngOnInit() {
  this.route.parent.data.forEach((data: { partner: Partner }) => {
    this.partner = data.partner;
  });
}
yorch
  • 7,170
  • 8
  • 32
  • 38
  • I am trying to implement this code. What is route declared to be here? I find that I need to use this.route.snapshot.data, but then there is no forEach on this data property. – DeborahK Jan 05 '17 at 23:04
  • Never mind. I used this: this.route.parent.snapshot.data['partner'] – DeborahK Jan 05 '17 at 23:43
-1

Another way to pass data from the parent to the child is to use @Input.

@Component({
   selector: 'partner',
   template: 'partner.html'
   directives: [LoginComponent]
})
export class PartnerComponent {
   partner: string;
}

@Component({
   selector: 'login',
   template: 'login.html'
})
export class LoginComponent {
   @Input() loginPartner: string; //passed in from the parent
}

//partner.html
<div>
   <login [login-partner]='partner'></login>
</div>

Reference https://angular.io/docs/ts/latest/cookbook/component-communication.html

Kevin Le
  • 846
  • 8
  • 17
  • 1
    Right, but that assumes a static-ish route. the routes that I described are dynamic and it can be `/:partner/login` or `/:partner/tools`, etc. With your approach, you have to handle the child routes yourself. That's the role of the `Router` and the `` to do that for you. As you can tell in the link you provided, they don't mention routing. In routing, the selector is unnecessary as the component is never in HTML. Looks like they also recommend the service approach. Will probably do that... – Eric Liprandi Sep 03 '16 at 02:53