作为@Input的子组件的角度传递回调函数

时间:2016-02-11 00:25:25

标签: typescript angular

AngularJS有&您可以将回调传递给指令的参数(例如AngularJS way of callbacks。是否可以将回调作为@Input传递给Angular组件(如下所示)?如果不是最接近的东西AngularJS的作用是什么?

@Component({
    selector: 'suggestion-menu',
    providers: [SuggestService],
    template: `
    <div (mousedown)="suggestionWasClicked(suggestion)">
    </div>`,
    changeDetection: ChangeDetectionStrategy.Default
})
export class SuggestionMenuComponent {
    @Input() callback: Function;

    suggestionWasClicked(clickedEntry: SomeModel): void {
        this.callback(clickedEntry, this.query);
    }
}


<suggestion-menu callback="insertSuggestion">
</suggestion-menu>

10 个答案:

答案 0 :(得分:205)

我认为这是一个糟糕的解决方案。如果您想将一个函数传递给@Input()的组件,@Output()装饰器就是您要找的。

export class SuggestionMenuComponent {
    @Output() onSuggest: EventEmitter<any> = new EventEmitter();

    suggestionWasClicked(clickedEntry: SomeModel): void {
        this.onSuggest.emit([clickedEntry, this.query]);
    }
}

<suggestion-menu (onSuggest)="insertSuggestion($event[0],$event[1])">
</suggestion-menu>

答案 1 :(得分:94)

<强>更新

当Angular 2仍处于alpha状态并且许多功能不可用/未记录时,提交了此答案。虽然以下仍然可行,但这种方法现在已经过时了。我强烈推荐以下接受的答案。

原始答案

是的,实际上是,但是你需要确保它的范围是正确的。为此,我使用了一个属性来确保this表示我想要它。

@Component({
  ...
  template: '<child [myCallback]="theBoundCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent{
  public theBoundCallback: Function;

  public ngOnInit(){
    this.theBoundCallback = this.theCallback.bind(this);
  }

  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}

答案 2 :(得分:25)

SnareChops给出答案的替代方案。

您可以在模板中使用.bind(this)来产生相同的效果。它可能不是那么干净但它可以节省几行。我目前处于角度2.4.0

@Component({
  ...
  template: '<child [myCallback]="theCallback.bind(this)"></child>',
  directives: [ChildComponent]
})
export class ParentComponent {

  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}

答案 3 :(得分:4)

作为一个例子,我使用的是登录模式窗口,其中模态窗口是父窗口,登录窗体是子窗口,登录按钮调用回模态父窗口关闭功能。

父模态包含关闭模态的功能。此父级将close函数传递给登录子组件。

import { Component} from '@angular/core';
import { LoginFormComponent } from './login-form.component'

@Component({
  selector: 'my-modal',
  template: `<modal #modal>
      <login-form (onClose)="onClose($event)" ></login-form>
    </modal>`
})
export class ParentModalComponent {
  modal: {...};

  onClose() {
    this.modal.close();
  }
}

在子登录组件提交登录表单后,它使用父回调函数关闭父模式

import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'login-form',
  template: `<form (ngSubmit)="onSubmit()" #loginForm="ngForm">
      <button type="submit">Submit</button>
    </form>`
})
export class ChildLoginComponent {
  @Output() onClose = new EventEmitter();
  submitted = false;

  onSubmit() {
    this.onClose.emit();
    this.submitted = true;
  }
}

答案 4 :(得分:3)

在某些(罕见)情况下,您可能需要由父组件执行业务逻辑。在下面的示例中,我们有一个子组件,该组件根据父组件提供的逻辑来渲染表行:

@Component({
  ...
  template: '<table-component [getRowColor]="getColor"></table-component>',
  directives: [TableComponent]
})
export class ParentComponent {

 // Pay attention on the way this function is declared. Using fat arrow (=>) declaration 
 // we can 'fixate' the context of `getColor` function
 // so that it is bound to ParentComponent as if .bind(this) was used.
 getColor = (row: Row) => {
    return this.fancyColorService.getUserFavoriteColor(row);
 }

}

@Component({...})
export class TableComponent{
  // This will be bound to the ParentComponent.getColor. 
  // I found this way of declaration a bit safer and convenient than just raw Function declaration
  @Input('getRowColor') getRowColor: (row: Row) => Color;

  renderRow(){
    ....
    // Notice that `getRowColor` function holds parent's context because of a fat arrow function used in the parent
    const color = this.getRowColor(row);
    renderRow(row, color);
  }
}

也许这不是理想的情况,可以通过更好的方法来完成,但是我只想在这里演示两件事:

  1. 使用胖箭头(=>)代替.bind(this)函数可保持正确的上下文;
  2. 子组件中回调函数的Typesafe声明。

答案 5 :(得分:1)

在模板中使用.bind传递带有参数的方法

@Component({
  ...
  template: '<child [action]="foo.bind(this, 'someArgument')"></child>',
  ...
})
export class ParentComponent {
  public foo(someParameter: string){
    ...
  }
}

@Component({...})
export class ChildComponent{

  @Input()
  public action: Function; 

  ...
}

答案 6 :(得分:1)

Max Fahl给出的答案的替代方法。

您可以在父组件中将回调函数定义为箭头函数,这样就无需绑定它。

@Component({
  ...
  // unlike this, template: '<child [myCallback]="theCallback.bind(this)"></child>',
  template: '<child [myCallback]="theCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent {

   // unlike this, public theCallback(){
   public theCallback = () => {
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}

答案 7 :(得分:0)

使用可观察模式。您可以将Observable值(而非Subject)放入Input参数并从父组件进行管理。您不需要回调函数。

查看示例:https://stackoverflow.com/a/49662611/4604351

答案 8 :(得分:0)

另一种选择。

OP询问了使用回调的方法。在这种情况下,他专门指的是处理事件的函数(在他的示例中为click事件),应将其视为@serginho建议的接受答案:使用@OutputEventEmitter

但是,回调和事件之间是有区别的:通过回调,您的子组件可以从父级检索一些反馈或信息,但是一个事件只能通知发生了某些事情而无需任何反馈。

在某些情况下,需要反馈。获取颜色或组件需要处理的元素列表。您可以按照某些答案的建议使用绑定函数,也可以使用接口(这始终是我的偏好)。

示例

让我们假设您有一个通用组件,该组件在要与具有这些字段的所有数据库表一起使用的元素{id,name}列表上运行。该组件应:

  • 检索一系列元素(页面)并将其显示在列表中
  • 允许删除元素
  • 通知已单击某个元素,因此父级可以采取一些措施。
  • 允许检索元素的下一页。

子组件

使用常规绑定,我们将需要1个@Input()和3个@Output()参数(但没有父级的任何反馈)。例如<list-ctrl [items]="list" (itemClicked)="click($event)" (itemRemoved)="removeItem($event)" (loadNextPage)="load($event)" ...>,但是创建一个界面我们只需要一个@Input()

import {Component, Input, OnInit} from '@angular/core';

export interface IdName{
  id: number;
  name: string;
}

export interface IListComponentCallback<T extends IdName> {
    getList(page: number, limit: number): Promise< T[] >;
    removeItem(item: T): Promise<boolean>;
    click(item: T): void;
}

@Component({
    selector: 'list-ctrl',
    template: `
      <button class="item" (click)="loadMore()">Load page {{page+1}}</button>
      <div class="item" *ngFor="let item of list">
          <button (click)="onDel(item)">DEL</button>
          <div (click)="onClick(item)">
            Id: {{item.id}}, Name: "{{item.name}}"
          </div>
      </div>
    `,
    styles: [`
      .item{ margin: -1px .25rem 0; border: 1px solid #888; padding: .5rem; width: 100%; cursor:pointer; }
      .item > button{ float: right; }
      button.item{margin:.25rem;}
    `]
})
export class ListComponent implements OnInit {
    @Input() callback: IListComponentCallback<IdName>; // <-- CALLBACK
    list: IdName[];
    page = -1; 
    limit = 10;

    async ngOnInit() {
      this.loadMore();
    }
    onClick(item: IdName) {
      this.callback.click(item);   
    }
    async onDel(item: IdName){ 
        if(await this.callback.removeItem(item)) {
          const i = this.list.findIndex(i=>i.id == item.id);
          this.list.splice(i, 1);
        }
    }
    async loadMore(){
      this.page++;
      this.list = await this.callback.getList(this.page, this.limit); 
    }
}

父组件

现在我们可以在父级中使用列表组件。

import { Component } from "@angular/core";
import { SuggestionService } from "./suggestion.service";
import { IdName, IListComponentCallback } from "./list.component";

type Suggestion = IdName;

@Component({
  selector: "my-app",
  template: `
    <list-ctrl class="left" [callback]="this"></list-ctrl>
    <div class="right" *ngIf="msg">{{ msg }}<br/><pre>{{item|json}}</pre></div>
  `,
  styles:[`
    .left{ width: 50%; }
    .left,.right{ color: blue; display: inline-block; vertical-align: top}
    .right{max-width:50%;overflow-x:scroll;padding-left:1rem}
  `]
})
export class ParentComponent implements IListComponentCallback<Suggestion> {
  msg: string;
  item: Suggestion;

  constructor(private suggApi: SuggestionService) {}

  getList(page: number, limit: number): Promise<Suggestion[]> {
    return this.suggApi.getSuggestions(page, limit);
  }
  removeItem(item: Suggestion): Promise<boolean> {
    return this.suggApi.removeSuggestion(item.id)
      .then(() => {
        this.showMessage('removed', item);
        return true;
      })
      .catch(() => false);
  }
  click(item: Suggestion): void {
    this.showMessage('clicked', item);
  }
  private showMessage(msg: string, item: Suggestion) {
    this.item = item;
    this.msg = 'last ' + msg;
  }
}

请注意,<list-ctrl>接收this(父组件)作为回调对象。 另一个好处是不需要发送父实例,它可以是服务,也可以是使用接口的任何对象(如果您的用例允许)。

完整的示例在this stackblitz上。

答案 9 :(得分:-3)

目前的答案可以简化为......

COUNTESS  
This  
young  
gentlewoman  
had  
a  
father,--O,  
that