Another alternative.
The OP asked a way to use a callback. In this case he was referring specifically to a function that process an event (in his example: a click event), which shall be treated as the accepted answer from @serginho suggests: with @Output
and EventEmitter
.
However, there is a difference between a callback and an event: With a callback your child component can retrieve some feedback or information from the parent, but an event only can inform that something happened without expect any feedback.
There are use cases where a feedback is necessary, ex. get a color, or a list of elements that the component needs to handle. You can use bound functions as some answers have suggested, or you can use interfaces (that's always my preference).
Example
Let's suppose you have a generic component that operates over a list of elements {id, name} that you want to use with all your database tables that have these fields. This component should:
- retrieve a range of elements (page) and show them in a list
- allow remove an element
- inform that an element was clicked, so the parent can take some action(s).
- allow retrieve the next page of elements.
Child Component
Using normal binding we would need 1 @Input()
and 3 @Output()
parameters (but without any feedback from the parent). Ex. <list-ctrl [items]="list" (itemClicked)="click($event)" (itemRemoved)="removeItem($event)" (loadNextPage)="load($event)" ...>
, but creating an interface we will need only one @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);
}
}
Parent Component
Now we can use the list component in the parent.
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;
}
}
Note that the <list-ctrl>
receives this
(parent component) as the callback object.
One additional advantage is that it's not required to send the parent instance, it can be a service or any object that implements the interface if your use case allows it.
The complete example is on this stackblitz.