Komunikacja między komponentami to kluczowy aspekt budowania aplikacji w Angularze, umożliwiający wymianę danych i wyzwalanie zachowań między różnymi częściami aplikacji. Istnieje kilka głównych sposobów na realizację tej komunikacji, w zależności od relacji między komponentami: komunikacja z rodzica na dziecko, z dziecka na rodzica oraz między komponentami rodzeństwa.
1. Komunikacja z rodzica na dziecko
Najprostszym sposobem przekazywania danych z komponentu rodzica do dziecka jest użycie @Input(). To pozwala na jednokierunkowe przekazywanie danych.
Dekorator @Input()
w Angularze jest używany do przekazywania danych z komponentu rodzica do komponentu dziecka. To fundamentalny mechanizm komunikacji w Angularze, który umożliwia jednokierunkowe wiązanie danych (z góry na dół) między komponentami. Dzięki temu można efektywnie oddzielić komponenty, zapewniając im niezbędne dane do wyświetlenia lub dalszej logiki, bez konieczności udostępniania całego kontekstu aplikacji.
@Input()
pozwala komponentowi dziecku deklarować właściwości, które mogą przyjąć wartości z zewnątrz, czyli od komponentu rodzica. Rodzic może przekazać wartość do dziecka, dodając atrybut do selektora dziecka w swoim szablonie, który odpowiada nazwie właściwości oznaczonej jako @Input()
.
Przykład
Rodzic komponent:
// parent.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-parent',
standalone: true,
imports: [CommonModule],
template: `
<app-child [message]="parentMessage"></app-child>
`,
})
export class ParentComponent {
parentMessage = 'Wiadomość od rodzica';
}
Dziecko komponent:
// child.component.ts
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-child',
standalone: true,
imports: [CommonModule],
template: `<p>Odebrane: {{ message }}</p>`,
})
export class ChildComponent {
@Input() message!: string;
}
Użycie znaku wykrzyknika (!) przy @Input()
Znak wykrzyknika (!), używany przy właściwościach w TypeScript (tzw. „definite assignment assertion”), informuje kompilator, że właściwość będzie na pewno zainicjowana, ale nie przez konstruktor klasy, a przez Angulara w trakcie procesu ustawiania danych wejściowych (@Input()
). Dzięki temu unikamy błędów kompilacji związanych z surowym sprawdzaniem typów i inicjalizacji właściwości, które mogą wystąpić, gdy strictPropertyInitialization
jest włączone.
W powyższym przykładzie, deklaracja message!: string;
informuje kompilator, że właściwość message
zostanie zainicjowana przez Angular przy ustawianiu wartości wejściowej, a nie przez konstruktor komponentu. Dzięki temu nie musimy dawać wartości domyślnej lub fałszywego inicjalizatora, co pozwala zachować czystość kodu i zgodność z rzeczywistymi intencjami logiki aplikacji.
Komunikacja z dziecka na rodzica
Aby przekazać dane z komponentu dziecka do rodzica, można użyć @Output() i EventEmitter. To umożliwia dziecku wysyłanie zdarzeń, które rodzic może nasłuchiwać.
Dekorator @Output()
jest używany w klasach komponentów do deklaracji właściwości jako „wyjścia” komponentu. Właściwości te są zazwyczaj instancjami EventEmitter
i służą do emitowania danych z komponentu dziecka do komponentu rodzica. Są one podłączane do zdarzeń w szablonie rodzica, umożliwiając rodzicom reagowanie na zmiany lub akcje wykonane w komponencie dziecka.
EventEmitter
jest klasą z Angulara, która pozwala komponentom emitować zdarzenia niestandardowe. Można się na nie subskrybować, co jest analogiczne do słuchania na zdarzenia DOM jak click
czy hover
, ale w kontekście danych komponentów Angulara. EventEmitter
jest często używany z @Output()
do tworzenia niestandardowych zdarzeń, które mogą być przechwytywane przez komponenty rodzica.
Przykład
Dziecko komponent:
// child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-child',
standalone: true,
imports: [CommonModule],
template: `<button (click)="sendEvent()">Wyślij do rodzica</button>`,
})
export class ChildComponent {
@Output() notify: EventEmitter<string> = new EventEmitter<string>();
sendEvent() {
this.notify.emit('Wiadomość wysłana do rodzica');
}
}
Rodzic komponent:
// parent.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-parent',
standalone: true,
imports: [CommonModule],
template: `
<app-child (notify)="handleEvent($event)"></app-child>
<p>Otrzymano: {{ messageFromChild }}</p>
`,
})
export class ParentComponent {
messageFromChild: string;
handleEvent(event: string) {
this.messageFromChild = event;
}
}
Komunikacja między komponentami rodzeństwa
Do komunikacji między komponentami, które nie mają bezpośredniej relacji rodzic-dziecko, można wykorzystać usługę (Service). Usługi w Angularze pozwalają na współdzielenie danych i logiki pomiędzy dowolnymi komponentami.
Przykład
Shared Service:
// shared.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SharedService {
private messageSource = new Subject<string>();
message$ = this.messageSource.asObservable();
sendMessage(message: string) {
this.messageSource.next(message);
}
}
Komponent wysyłający:
// sender.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedService } from './shared.service';
@Component({
selector: 'app-sender',
standalone: true,
imports: [CommonModule],
template: `<button (click)="sendMessage()">Wyślij</button>`,
})
export class SenderComponent {
constructor(private sharedService: SharedService) {}
sendMessage() {
this.sharedService.sendMessage('Wiadomość od nadawcy');
}
}
Komponent odbierający:
// receiver.component.ts
import { Component, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedService } from './shared.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-receiver',
standalone: true,
imports: [CommonModule],
template: `<p>Odebrane: {{ message }}</p>`,
})
export class ReceiverComponent implements OnDestroy {
message: string;
private subscription: Subscription;
constructor(private sharedService: SharedService) {
this.subscription = this.sharedService.message$.subscribe(
message => this.message = message
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}