erichmuller
erichmuller

Reputation: 37

Angular2: Passing an object to bind event from parent to child

The binding works fine for title, subtitle, button.icon and button.name but not for button.action

parent.component.html

    <app-title [title]="title" [subtitle]="subtitle" [buttons]="buttons"></app-title>

parent.component.ts

export class ParentComponent {

actionOne() {
    ...
}

title = 'Title';
subtitle = 'Subtitle';
buttons = [
    { 'name': 'Name1', 'icon': 'Icon1', 'action': 'actionOne()'},
    { 'name': 'Name2', 'icon': 'Icon2', 'action': 'actionTwo()'},
    { 'name': 'Name3', 'icon': 'Icon3', 'action': 'actionThree()'}
];

}

child.component.html

<section>
<div class="text-wrapper">
        <h1>{{ title }}</h1>
        <h2 *ngIf="subtitle">{{ subtitle }}</h2>
</div>
 <template *ngIf="buttons">
        <div class="buttons-wrapper">
            <button *ngFor="let button of buttons" md-raised-button (click)="button.action"><md-icon *ngIf="button.icon">{{ button.icon }}</md-icon>{{ button.name }}</button>
        </div>
    </template>
</div>

child.component.ts

export class ChildComponent  {

@Input() title:string;
@Input() subtitle:string;
@Input() buttons:string;

}

Upvotes: 1

Views: 2447

Answers (2)

penleychan
penleychan

Reputation: 5470

This is possible to do:

Button interface:

export interface IButton {
  Name: string;
  Icon: string;
  Action: Function
}

Parent component:

@Component({
...
})
export class ParentComponent implements OnInit {
  buttons: IButton[] = [
    {
      Name: 'Hello',
      Icon: 'Test',
      Action: this.actionOne.bind(this) // we need to make sure this is scoped correctly
    }
  ];

  actionOne(){
    console.log('This is from action One');
  }

  constructor() { }

  ngOnInit() {

  }
}

Child component

@Component({
...
})
export class ChildComponent implements OnInit {
  @Input() buttons: IButton[];

  constructor() { }

  ngOnInit() {

  }
}

Child html

<div *ngIf="buttons">
  <button *ngFor="let button of buttons" (click)="button.Action()">{{button.Name}}</button>
</div>

Hope that helps

Upvotes: 1

AngularChef
AngularChef

Reputation: 14087

Your code cannot work, since you are passing string literals, not method references, to the child. For instance, in the button.action property, your ChildComponent sees the string literal "actionOne()", not the method actionOne().

In order to pass the method references when declaring the buttons, you'd have to remove the quotes and parentheses around the method names and prefix them with this.:

buttons = [
    { 'name': 'Name1', 'icon': 'Icon1', 'action': this.actionOne },
    { 'name': 'Name2', 'icon': 'Icon2', 'action': this.actionTwo },
    { 'name': 'Name3', 'icon': 'Icon3', 'action': this.actionThree }
]

But still, it won't work as the context for this will be lost in the ChildComponent.

What I would do is use string literals in the buttons declarations (similar to your code, but WITHOUT the parentheses, e.g. 'actionOne', 'actionTwo'...) and whenever a button is clicked in the child, I would emit an @Output event to the parent:

@Component({
  template: `
    <button (click)="buttonClicked.emit(button.action)">
      {{ button.name }}
    </button>
  `
})
export class ChildComponent  {
  @Output() buttonClicked: EventEmitter<string> = new EventEmitter<string>();
}

Notice that I'm passing the button.action property (string) when emitting the event.

Now, the ParentComponent can listen to that event and use the received string as an identifier to decide which local method to call:

@Component({
  template: `
    <child-comp (buttonClicked)="handleAction($event)"></child-comp>
  `
})
export class ParentComponent  {

  handleAction(actionName) {
    // Here, put some logic to call a local method based on `actionName`
    // Something like this[actionName]();
  }

  actionOne() {
      ...
  }

  actionTwo() {
      ...
  }

}

Upvotes: 0

Related Questions