Outra alternativa.
O OP pediu uma maneira de usar um retorno de chamada. Nesse caso, ele estava se referindo especificamente a uma função que processa um evento (em seu exemplo: um evento de clique), que deve ser tratado como a resposta aceita por @serginho sugere: with @Output
e EventEmitter
.
No entanto, há uma diferença entre um retorno de chamada e um evento: Com um retorno de chamada, o componente filho pode recuperar alguns comentários ou informações dos pais, mas um evento pode apenas informar que algo aconteceu sem esperar nenhum retorno.
Existem casos de uso em que um feedback é necessário, por exemplo. obtenha uma cor ou uma lista de elementos que o componente precisa manipular. Você pode usar funções vinculadas, como algumas respostas sugeriram, ou pode usar interfaces (essa é sempre a minha preferência).
Exemplo
Vamos supor que você tenha um componente genérico que opera sobre uma lista de elementos {id, nome} que você deseja usar com todas as suas tabelas de banco de dados que possuem esses campos. Este componente deve:
- recuperar um intervalo de elementos (página) e mostrá-los em uma lista
- permitir remover um elemento
- informe que um elemento foi clicado, para que o pai possa executar algumas ações.
- permitir recuperar a próxima página de elementos.
Componente filho
Usando a ligação normal, precisaríamos de 1 @Input()
e 3 @Output()
parâmetros (mas sem nenhum feedback do pai). Ex. <list-ctrl [items]="list" (itemClicked)="click($event)" (itemRemoved)="removeItem($event)" (loadNextPage)="load($event)" ...>
, mas, ao criar uma interface, precisaremos de apenas uma @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);
}
}
Componente pai
Agora podemos usar o componente list no pai.
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;
}
}
Observe que o <list-ctrl>
recebimento this
(componente pai) é o objeto de retorno de chamada. Uma vantagem adicional é que não é necessário enviar a instância pai, ela pode ser um serviço ou qualquer objeto que implemente a interface, se o seu caso de uso permitir.
O exemplo completo está neste stackblitz .
@Input
modo sugerido fez com que meu código fosse complicado e não fosse fácil de manter.@Output
s são uma maneira muito mais natural de fazer o que eu quero. Como resultado eu mudei a resposta aceita