Drzewo komponentów
Na aplikację Angular można patrzeć jak na drzewo zagnieżdżonych komponentów. Na przykład:
<tag-todo-app>
<tag-todo-list>
<tag-todo-item></tag-todo-item>
<tag-todo-item></tag-todo-item>
<tag-todo-item></tag-todo-item>
</tag-todo-list>
<tag-todo-form></tag-todo-form>
</tag-todo-app>
W katalogu głównym mamy tag-todo-app
, która składa się ztag-todo-list
oraz tag-todo-form
. Na liście mamy kilka tag-todo-item
. Każdy z tych komponentów jest widoczny dla użytkownika, który może wchodzić w interakcje z tymi komponentami i wykonywać działania.
Przekazywanie danych do komponentu
Istnieją dwa sposoby przekazywania danych do komponentu za pomocą powiązania właściwości
i powiązania zdarzeń
. W Angular wykrywanie zmian danych i zdarzeń odbywa się z góry na dół od rodzica do dzieci. Jednak w przypadku zdarzeń Angular może skorzystać z modelu zdarzeń DOM, w którym wydarzenia przepływają od dołu do góry, od dziecka do rodzica.
Dekorator @Input()
definiuje zestaw parametrów, które można przekazać z nadrzędnego komponentu. Na przykład możemy zmodyfikować komponent HelloComponent
, abyname
mogło być dostarczone przez obiekt nadrzędny.
import { Component, Input } from '@angular/core';
@Component({
selector: 'tag-hello',
template: '<p>Witaj, {{name}}!</p>',
})
export class HelloComponent {
@Input() name: string;
}
Powodem tworzenia komponentów jest nie tylko hermetyzacja, ale także możliwość ponownego wykorzystania. Wejścia pozwalają nam skonfigurować konkretne wystąpienie komponentu.
Możemy teraz korzystać z naszego komponentu w następujący sposób:
<div>
<!-- Powiązanie z łańcuchem znaów -->
<tag-hello name="Świecie"></tag-hello>
<!-- Powiązanie ze zmienną rodzica -->
<tag-hello [name]="helloName"></tag-hello>
</div>
Zwróć uwagę na możliwość wiązania wartości wejścia (@Input) potomka ze zmienną (w przykładzie: helloName).
Zobacz przykład [004]
W przeciwieństwie do Angular 1.x jest to jednokierunkowe wiązanie.
Reagowanie na zdarzenia w komponencie
Procedura obsługi zdarzenia jest określona w szablonie za pomocą nawiasów okrągłych: (zdarzenie)="funkcja()"
. Funkcja obsługi zdarzeń jest następnie kodowana w klasie komponentu.
import { Component } from '@angular/core';
@Component({
selector: 'tag-counter',
template: `
<div>
<p>Count: {{num}}</p>
<button (click)="increment()">Zwiększ</button>
</div>
`
})
export class CounterComponent {
num = 0;
increment() {
this.num++;
}
}
Zobacz przykład [005]
Aby wysłać dane z komponentów przez wyjścia, stosujemy dekorator Output.
Uwaga: alternatywą dla takiego sposobu pisania aplikacji jest użycie wspólnego dla wszystkich komponentów stanu (vide projekt Redux). Bowiem przekazywanie zdarzeń z dołu ku górze znacząco komplikuje program.
Wyjście przyjmuje listę parametrów, które komponent udostępnia swojemu rodzicowi.
app/counter.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'tag-counter',
templateUrl: 'counter.component.html'
})
export class CounterComponent {
@Input() count = 0;
@Output() result = new EventEmitter<number>();
increment() {
this.count++;
this.result.emit(this.count);
}
}
Do przekazywania nietypowych danych służy klasa EventEmitter który jest rozszerzeniem klasy Subject (z biblioteki RxJS). Może ona rozsyłać zdarzenia niestandardowe przy użyciu swojej metody emit() - jak w powyższym przykładzie. Rozsyłanie następuje w funkcji obsługi zdarzenia (increment) komponentu. Domyślnie nazwa zdarzenia niestandardowego jest taka sama jak nazwa wyjścia (tu: result). Dekorator @Output może ją zmienić.
app/counter.component.html
<div>
<p>Licznik: {{ count }}</p>
<button (click)="increment()">Zwiększ</button>
</div>
app/app.component.ts
import { Component, OnChanges } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnChanges {
num = 1;
parentCount = 0;
ngOnChange(val: number) {
this.parentCount = val;
}
}
Zdarzenie ngOnChanges jest wywoływane, gdy następuje zmiana jakiejś własności wejściowej (@Input - zob. cykl życia komponentu). Aby je obsłużyć, musimy dodać do deklaracji klasy interfejs o nazwie która odpowiada nazwie funkcji obsługi zdarzenia bez ng. W tym przypadku ngOnChanges() => OnChanges.
app/app.component.html
<div>
Rodzic - liczba: {{ num }}<br>
Rodzic - licznik: {{ parentCount }}
<tag-counter [count]="num" (result)="ngOnChange($event)">
</tag-counter>
</div>
Zmienna $event
reprezentuje zdarzenie. Może to być natywne zdarzenie przeglądarki (zob. https://developer.mozilla.org/en-US/docs/Web/Events\ lub własne zdarzenie - tu "result".
W szablonach używamy nawiasów klamrowych [] do przekazywania danych wejściowych i zwykłych nawiasów () do obsługi wyjść.
Zobacz przykład [006]
Razem zestaw powiązań wejścia-wyjścia definiuje publiczny interfejs API twojego komponentu.