8

All the tutorials and answers that I have found show only how to pass a variable from parent component to child component using inputs but what is this child component is contained within the router outlet and not directly in the parent template ??

e.g:

Main component

@Component({
  selector: 'my-app',
  template: `
          Main page
          <router-outlet></router-outlet>
              `,
  directives: [ROUTER_DIRECTIVES]
})

@RouteConfig([
    { path: '/contact', name: 'Contact', component: ContactComponent},
 ])

export class AppComponent{
  public num:Number = 123;
}





@Component({
   selector: 'contact-page',
   template: 'contact page'
})
export class ContactComponent{
   public num:Number;
}

So in this example the main component template contain a router outlet where the child contact component will be rendered but how can I get variable "num" value in the child component evaluated inside a router outlet from the parent app component ??

Joseph Girgis
  • 3,115
  • 4
  • 19
  • 20
  • 1
    see also http://stackoverflow.com/questions/34363792/angular2-using-inputs-with-router-outlets – phil294 Dec 20 '16 at 17:32

3 Answers3

4

I just stumbled over this question, here is how I have solved similar issues.

I would use a service to solve this. Then it is possible for all children and the parent to set the property, and the changes are propagated out for all subscribers. First I would create a service with a private BehaviorSubject which have a public getter and setter, to encapsulate ReplaySubject and only return Observable:

private _property$: BehaviorSubject<number> = new BehaviorSubject(1);

set property(value: number) {
    this._property$.next(value);
}

get property$(): Observable<number> {
    return this._property$.asObservable();
}

The reason for using new BehaviorSubject(1), is to set the initial value to 1, so there is something to subscribe to.

In the parents onInit, I would se the default value of property (num):

private _propertySubscribtion: Subscription

ngOnInit() {
    // set default value to 5
    this._componentService.property = 5;

    // If property is updated outside parent
    this._componentService.property$.subscribe(p => {
        this.property = p;
    });
}

ngOnDestroy() {
    this._propertySubscribtion.unsubscribe();
}

In one or more of of the child components, it possible to subscribe for changes:

private _propertySubscribtion: Subscription

ngOnInit() {
    this._propertySubscribtion = this._componentService.property$.subscribe(p => {
        this.property = p;
    });
}

ngOnDestroy() {
    this._propertySubscribtion.unsubscribe();
}

And if some child or parent updates the property:

updateProperty() {
    // update property
    this._componentService.property = 8;
}

All subscribers will know about it.

DNRN
  • 2,397
  • 4
  • 30
  • 48
  • Is there any method that removes the property value after first emition without updating the property? – PaladiN Jan 05 '17 at 09:17
  • Would it be sufficient to set the value to null. I sometimes use the null value as an initial value before a http request is finished. – DNRN Jan 05 '17 at 12:57
  • Yeah. I have also set it to null at initially and then again set the value to null after the operation. Thanks for it... – PaladiN Jan 05 '17 at 13:04
  • 1
    Just remember to unsubscribe, I don't know your setup but to ensure your component don't get called when it isn't needed :) – DNRN Jan 05 '17 at 13:11
  • Your solution helped alot. Is there any other method to pass values other than this one??? – PaladiN Jan 06 '17 at 05:40
0

Currently you can't bind to components added by the router. Use a shared service instead (there are tons of examples here on SO already) to pass data from or to the added component or to subscribe to events.

See also this issue https://github.com/angular/angular/issues/4452 especially this comment https://github.com/angular/angular/issues/4452#issuecomment-153889558

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • shared service is not ideal if you think that for example you have a service that gets all your app settings from a database and you need to display some of these settings on the main component like displaying an email on the top of the page or a phone number and then you need to display the same on a contact page through the router outlet if you used a service you will be doing an ajax call twice on the same page to get the same details. hope you see my point. – Joseph Girgis Feb 23 '16 at 13:18
  • 1
    Actually I don't see how using a service and "doing an ajax call twice" are related. You can use a service just to pass data from the `ParentComponent` (containing the `` and the component added to the `` by adding it to the providers list of `ParentComponent`. This way the service is only shared between `ParentComponent` and its descendants. The shared service can itself depend on a global service that does the ajax calls and for example returns cached results if the data was already fetched earlier. – Günter Zöchbauer Feb 23 '16 at 13:22
  • I understand your point but idea I am searching for is instead of having to fetch the results twice once from the parent and once from the child to get the same info. of course the idea of caching the results can prevent this double fetching but the idea that I can't wrap my head around if you fetch the results in the parent component and you can pass it to it's children why I can't do the same if the child in this case is contained within the router outlet ? there has to be a simpler logic to solve this issue. – Joseph Girgis Feb 23 '16 at 13:34
  • 2
    I guess you're just overthinking it. Binding currently just doesn't work with components added dynamically (by the router or DynamicComponentLoader). Instead of binding you use a service. This doesn't make much of a difference except it's more boilerplate (creating the service class, adding fields and event emitters or subjects, adding the service to providers, reading, writing, subscribing to inputs/outputs on parent and child). But besides that the only difference is that the service is also reachable by grandchildren while Binding only works with direct children. – Günter Zöchbauer Feb 23 '16 at 13:40
  • I agree this can work for this example but imagine that you want to change a page meta title contained in the parent component through a meta title variable from the routed component or you have a top cart in the parent component and you need to update it with new quantities and prices coming from a cart page where the cart is contained in the router outlet or even a breadcrumb...etc. many many example that you need some sort of interaction between a routed component and it's parent you can't isolate them in every case and use a shared service. hopefully you see the big problem here. – Joseph Girgis Feb 23 '16 at 16:15
  • Actually, no. I guess this would need a concrete example. You can inject a global service to a "local" one and then make the local service update the value in the global one when it changes. – Günter Zöchbauer Feb 23 '16 at 16:26
-1

If your data is immutable, you can use RouteData.

@Component({...})
@RouteConfig([
    { path: '/contact', name: 'Contact', component: ContactComponent, data: {num: 123}},
 ])
export class AppComponent {
}

@Component({...})
export class ContactComponent {
   public num:Number;
   constructor(data: RouteData) {
     this.num = data.get('num');
   }
}

Could be useful to pass some configuration options you don't want to hardcode in child routes. But if you're expecting data to change, you'll need some other method.

Sasxa
  • 40,334
  • 16
  • 88
  • 102