Angular用类似于AngularJS的方式将回调函数@Input传递给子组件

AngularJS有&参数,其中你可以传递一个回调到指令(例如AngularJS的回调方式。是否可以将回调函数作为@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>
321385 次浏览

更新

这个答案是在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;
...
}

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

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


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

我认为这是一个糟糕的解决方案。如果你想通过@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>

例如,我使用了一个登录模态窗口,其中模态窗口是父窗口,登录表单是子窗口,登录按钮调用了父窗口的关闭函数。

父模态包含关闭模态的函数。这个父组件将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;
}
}

SnareChops给出的另一个答案。

你可以在模板中使用.bind(this)来达到同样的效果。它可能没有那么干净,但它节省了几行。我现在使用的是angular 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;
...
}

在某些情况下,您可能需要由父组件执行业务逻辑。在下面的例子中,我们有一个子组件,它根据父组件提供的逻辑来呈现表行:

@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. 子组件中回调函数的类型安全声明。

传递带有参数的方法,在模板中使用.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;


...
}

使用可观察模式。你可以把可观察值(不是主题)放入输入参数,并从父组件管理它。你不需要回调函数。

参见示例:https://stackoverflow.com/a/49662611/4604351

与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;
...
}

另一个选择。

OP询问了一种使用回调的方法。在这种情况下,他特别提到了一个处理事件的函数(在他的例子中:一个点击事件),这应该被视为@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);
}
}

父组件

现在我们可以在父类中使用list组件。

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(父组件)作为回调对象。 一个额外的好处是它不需要发送父实例,它可以是一个服务或任何实现接口的对象,如果你的用例允许的话

完整的例子在这stackblitz上。

以下是我在Angular 13中的工作(截至2022年3月)。

< >强注:-这和其他人的回答或多或少有些相似。添加这个答案只是为了让人们知道它可以在Angular 13中工作

将函数定义为平的箭头,而不是父组件中的常规函数。

callBackFn= (args: string): void => {
// callback code here
// This will work (Flat Arrow)
}
// callbackFn(args: string): void {
//   //This type of definition will not work.
// }

将回调函数作为属性传递给子组件

<app-child [callBack]=”callBackFn”></app-child>

在子组件中接收回调函数作为Input。该定义应该与父类中定义的定义相匹配。

@Input() callBack: (args: string) => void;

然后在子组件中调用该函数。你也可以称它为子组件模板。

this.callBack('Test');

<button (click)="callBack('Test')"></button>

但不确定这个方法好不好。 我在ReactJS中看到了类似的方法,它工作得很好,但仍然不确定它在angular中是如何工作的,以及它的影响是什么

任何关于此方法的评论都将被感激