2
votes

I have been looking for a solution for this for a while. I have tried a bunch of different things from @Input, @Query, dynamicContentLoader, @Inject, @Inject(forwardRef(() but haven't been able to figure this out yet.

My Example Structure:

parent.ts

import {Component} from 'angular2/core';

@Component({
    selector   : 'my-app',
    directives : [Child],
    template   : `<parent-component></parent-component>`
})

export class Parent
{
    private options:any;

    constructor()
    {
        this.options = {parentThing:true};
    }
}

child.ts

import {Component} from 'angular2/core';

@Component({
    selector   : 'parent-component',
    template   : `<child-component></child-component>`
})

export class Child
{
    constructor(private options:any) <- maybe I can pass them in here somehow?
    {
        // what I am trying to do is get options from
        // the parent component at this point
        // but I am not sure how to do this with Angular 2
        console.log(options) <- this should come from the parent

        // how can I get parent options here?
        // {parentThing:true}
    }
}

This is my current HTML output in the DOM, so this part is working as expected

<parent-component>
   <child-component></child-component>
</parent-component>

Question Summarized:

How can I pass options from a parent component to a child component and have those options available in the child constructor?

2
What's the problem with simple binding? @Input() in the child and <child [someInput]="someOption"? - Günter Zöchbauer
I have seen that mentioned many times. It very well could be the solution. The problem is for whatever reason I haven't understood how everything should hook together. I messed around with @Input for sometime.. But I think I am having a hard time wrapping my head around all of the implicit stuff that happens and how to get it to work properly. - Kris Hollenbeck
Basically I have tried all of these.. @Input, @Query, dynamicContentLoader, @Inject, @Inject(forwardRef(() But I haven't been able to get them to do what I am trying to do. So I am not saying they can't. But more or less I minimized my question to show what type of end result I am trying to achieve. One of those could be the solution. - Kris Hollenbeck
Just saw, why do you need them available in the constructor? - Günter Zöchbauer
See my updated answer, but why do you need it in the constructor? - Günter Zöchbauer

2 Answers

7
votes

Parent to child is the simplest form of all but it's not available in the constructor, only in ngOnInit() (or later).

This only requires an @Input() someField; in the child component and using binding this can be passed from parent to children. Updates in the parent are updated in the child (not the other direction)

@Component({
  selector: 'child-component',
  template'<div>someValueFromParent: {{someValueFromParent}}</div>'
})  
class ChildComponent {
  @Input() someValueFromParent:string;

  ngOnInit() {
    console.log(this.someValue);
  }
}

@Component({
  selector: 'parent-component',
  template: '<child-component [someValueFromParent]="someValue"></child-component>'
})
class ParentComponent {
  someValue:string = 'abc';
}

to have it available in the constructor use a shared service. A shared service is injected into the constructor of both components. For injection to work the service needs to be registered in the parent component or above but not in the child. This way both get the same instance. Set a value in the parent and read it in the client.

@Injectable()
class SharedService {
  someValue:string;
}

@Component({
  selector: 'child-component',
  template: '<div>someValueFromParent: {{someValueFromParent}}</div>'
})  
class ChildComponent {
  constructor(private sharedService:SharedService) {
    this.someValue = sharedService.someValue;
  }
  someValue:string = 'abc';
}

@Component({
  selector: 'parent-component',
  providers: [SharedService],
  template: '<child-component></child-component>'
})
class ParentComponent {
  constructor(private sharedService:SharedService) {
    sharedService.someValue = this.someValue;
  }
  someValue:string = 'abc';
}

update

There is not much difference. For DI only the constructor can be used. If you want something injected it has to be through the constructor. ngOnInit() is called by Angular when additional initialization has taken place (like bindings being processed). For example if you make a network call it doesn't matter if you do it in the constructor on in ngOnInit because the call to the server is scheduled for later anyway (async). When the current sync task is completed, JavaScript looks for the next scheduled task and processes it (and so on). Therefore it's probably so that the server call initiated in the constructor is actually sent after ngOnInit() anyway no matter where you place it.

1
votes

You could use an @Input parameter:

import {Component,Input} from 'angular2/core';

@Component({
  selector   : 'parent-component',
  template   : `<child-component></child-component>`
})
export class Child {
  @Input()
  options:any;

  ngOnInit() {
    console.log(this.options);
  }
}

Notice that the value of options is available in the ngOnInit and not in the constructor. Have a look at the component lifecycle hooks for more details:

And provides the options as decribed below:

import {Component} from 'angular2/core';

@Component({
  selector   : 'my-app',
  directives : [Child],
  template   : `<parent-component [options]="options"></parent-component>`
})
export class Parent {
  private options:any;

  constructor() {
    this.options = {parentThing:true};
  }
}

If you want to implement custom events: child triggers an event and the parent register to be notified. Use @Output.