Eine andere Alternative.
Das OP fragte nach einer Möglichkeit, einen Rückruf zu verwenden. In diesem Fall bezog er sich speziell auf eine Funktion, die ein Ereignis verarbeitet (in seinem Beispiel: ein Klickereignis), die wie die von @serginho akzeptierte Antwort behandelt werden soll: mit @Output
und EventEmitter
.
Es gibt jedoch einen Unterschied zwischen einem Rückruf und einem Ereignis: Mit einem Rückruf kann Ihre untergeordnete Komponente Feedback oder Informationen vom übergeordneten Element abrufen, ein Ereignis kann jedoch nur darüber informieren, dass etwas passiert ist, ohne dass ein Feedback erwartet wird.
Es gibt Anwendungsfälle, in denen eine Rückmeldung erforderlich ist, z. Holen Sie sich eine Farbe oder eine Liste von Elementen, die die Komponente verarbeiten muss. Sie können gebundene Funktionen verwenden, wie einige Antworten vorgeschlagen haben, oder Sie können Schnittstellen verwenden (das ist immer meine Präferenz).
Beispiel
Angenommen, Sie haben eine generische Komponente, die eine Liste von Elementen {id, name} verarbeitet, die Sie für alle Datenbanktabellen mit diesen Feldern verwenden möchten. Diese Komponente sollte:
- Rufen Sie eine Reihe von Elementen (Seite) ab und zeigen Sie sie in einer Liste an
- Erlaube das Entfernen eines Elements
- Informieren Sie, dass auf ein Element geklickt wurde, damit das übergeordnete Element einige Aktionen ausführen kann.
- Ermöglichen das Abrufen der nächsten Seite mit Elementen.
Untergeordnete Komponente
Bei normaler Bindung benötigen wir 1 @Input()
und 3 @Output()
Parameter (jedoch ohne Rückmeldung des Elternteils). Ex. <list-ctrl [items]="list" (itemClicked)="click($event)" (itemRemoved)="removeItem($event)" (loadNextPage)="load($event)" ...>
, aber für die Erstellung einer Schnittstelle benötigen wir nur eine @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);
}
}
Übergeordnete Komponente
Jetzt können wir die Listenkomponente im übergeordneten Element verwenden.
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;
}
}
Beachten Sie, dass der <list-ctrl>
Empfang this
(übergeordnete Komponente) das Rückrufobjekt ist. Ein zusätzlicher Vorteil besteht darin, dass die übergeordnete Instanz nicht gesendet werden muss. Es kann sich um einen Dienst oder ein Objekt handeln, das die Schnittstelle implementiert, sofern Ihr Anwendungsfall dies zulässt.
Das vollständige Beispiel finden Sie auf diesem Stackblitz .
@Input
vorgeschlagene Weg meine Code-Spagetti gemacht und ist nicht einfach zu pflegen.@Output
s sind eine viel natürlichere Art, das zu tun, was ich will. Infolgedessen änderte ich die akzeptierte Antwort