Еще одна альтернатива.
ОП спросил способ использования обратного вызова. В этом случае он имел в виду конкретно функцию, которая обрабатывает событие (в его примере: событие щелчка), которое должно рассматриваться как принятый ответ от @serginho: with @Output
и EventEmitter
.
Однако существует разница между обратным вызовом и событием: с помощью обратного вызова ваш дочерний компонент может получить некоторую обратную связь или информацию от родителя, но только событие может сообщить, что что-то произошло, не ожидая никакой обратной связи.
Есть случаи, когда необходима обратная связь, напр. получить цвет или список элементов, которые компонент должен обработать. Вы можете использовать связанные функции, как предлагали некоторые ответы, или вы можете использовать интерфейсы (это всегда мое предпочтение).
пример
Предположим, у вас есть общий компонент, работающий со списком элементов {id, name}, который вы хотите использовать со всеми таблицами базы данных, в которых есть эти поля. Этот компонент должен:
- получить диапазон элементов (страницы) и показать их в списке
- разрешить удалить элемент
- сообщить, что элемент был нажат, чтобы родитель мог предпринять некоторые действия.
- Позволяет получить следующую страницу элементов.
Дочерний компонент
При обычном связывании нам понадобятся 1 @Input()
и 3 @Output()
параметра (но без обратной связи с родителем). Ex. <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
(родительский компонент) как объект обратного вызова. Еще одно преимущество заключается в том, что отправка родительского экземпляра не требуется, это может быть служба или любой объект, который реализует интерфейс, если это позволяет ваш сценарий использования.
Полный пример этого стекаблита .
@Input
предложенный способ сделал мой код спагетти и не простым в обслуживании.@Output
Это гораздо более естественный способ делать то, что я хочу. В результате я изменил принятый ответ