28 gru 2016
3 min

ANGULAR 2 – Bidirectional Service

Bidirectional Service – komunikacja komponentów poprzez serwis

Najczęściej nasze komponenty komunikują się przy użyciu dekoratorów Input() oraz Output(). Niestety niesie to za sobą pewne ograniczenia. Zarówno Input oraz Output, służą do komunikacji komponentów w bezpośredniej relacji rodzic-dziecko. Co jeśli chcemy komunikować komponenty niezależnie od relacji?

BIDIRECTIONAL SERVICE

Pomoże nam w tym dwukierunkowy serwis, który będzie współdzielony przez rodzic-komponent oraz jego dzieci.

Zasięg instancji serwisu dotyczy tylko danego drzewa komponentów, wszystkie komponenty, które nie należą do danego drzewa, nie mają dostępu do serwisu.

Na potrzeby artykułu, załączam Live Example.

LIVE EXAMPLE

Jakie wątki poruszymy w tym artykule?

  • napiszemy bi-directional service, który będzie zawierał metody do komunikacji naszych komponentów
  • wyślemy dane do komponentu trzy levele niżej w drzewie komponentów
  • komponent najniżej zagnieżdżony skomunikuje się z komponentem-rodzicem

W przedstawieniu graficznym, wykonamy następujące interakcje:

Bidirectional Service - interakcje

TWORZYMY SERWIS

Jeśli zapoznałeś się już ze plikami z live example na plunkerze, to skupimy się teraz na naszym dwu-kierunkowym serwisie (user-shared.service.ts):

@Injectable()
export class UserSharedService {

 private userSource = new Subject<User>();
 private messageSource = new Subject<string>();
 
 userContent$ = this.userSource.asObservable();
 messageSent$ = this.messageSource.asObservable();
 
 shareUser(user: User): void {
   this.userSource.next(user);
 }
 
 tellSomethingToParent(message: string): void {
   this.messageSource.next(message);
 }
}

Rozłóżmy kod na czynniki:

private userSource = new Subject<User>();

Subject jest klasą, która pochodzi z biblioteki RxJs, która implementuje reaktywne programowanie. Jest to skomplikowany i mocno rozbudowany koncept, stąd na potrzeby tego artykułu, nie będę wdawać się w szczegóły RxJs.  W przypadku zainteresowania tematem, załączam linka do Subject w dokumentacji RxJs.

Zatem krótko o Subject:

  • Subject jest strumieniem, jest to jednocześnie Observable oraz Observer
  • posiada między innymi metodę next(value), która powoduje emisję przekazanej wartości.
 userContent$ = this.userSource.asObservable();

Publiczne pole userContent$ posłuży nam do odbioru informacji w komponentach. Po subskrypcji do tego pola, będziemy mieli dostęp do danych.

 shareUser(user: User) {
   this.userSource.next(user);
 }

Metoda shareUser posłuży nam do emisji danych usera z poziomu UserDetailComponent.

Analogicznie z messageSource, które wykorzystamy do powiadomienia UserDetailComponent z poziomu ChildChildChildComponent.

WYSYŁAMY DANE DO KOMPONENTU 3 POZIOMY NIŻEJ

Serwis gotowy, więc czas przekazać dane Usera z komponentu UserDetailComponent do ChildChildChildComponent. W pliku user-detail.component.ts, zaczynamy od wstrzyknięcia naszego serwisu UserSharedService w konstruktorze oraz dodania go do providers.

@Component({
  ...
 providers: [UserSharedService]
})

constructor(private userSharedService: UserSharedService,
            private usersService: UsersService,
            private route: ActivatedRoute) {
 this.route.params
 .switchMap((params: Params) => this.usersService.getUser(+params['id']))
 .subscribe(user => {
   this.user = user
   this.userSharedService.shareUser(user);
 });
}

W powyższym kodzie, odebraliśmy konkretnego usera, metoda subscribe udostępnia nam jego dane. Podepniemy się w tym miejscu z wywołaniem metody shareUser naszego dwu-kierunkowego serwisu UserSharedService, który wyemituje przekazane dane. Pozostaje nam tylko teraz wykonać subskrypcję do $userContent, który umożliwi nam dostęp do wyemitowanych danych.

Przechodzimy do ChildChildChildComponent (child-child-child.component.ts)

subscription: Subscription;
constructor(private userSharedService: UserSharedService) {
 this.subscription = this.userSharedService.userContent$.subscribe((user) => {
   this.user = user;
 });
}

ngOnDestroy() {
 this.subscription.unsubscribe();
}

Zaczynamy od stworzenia subscription, które pochodzi z biblioteki RxJs. Subscription to obiekt, który reprezentuje zasoby w postaci Observables. Ma jedną bardzo ważną metodę – unsubscribe(), która umożliwia nam się pozbycia zasobów trzymanych w subscription, służy to przede wszystkim zapobieganiu wyciekom pamięci.

Generalnie w naszym przykładzie użycie subscription i metoda unsubscribe() nie ma znaczenia, ponieważ cykl życia ChildChildChildComponent jest identyczny jak UserComponent, także wycieki pamięci nie mają prawa bytu. Jednak w bardziej złożonych aplikacjach, warto pamiętać o użyciu unsubscribe(). Użyłem subscription wyłącznie demonstracyjnie.

Powyższy kod, zapewnił nam odebranie w ChildChildChildComponent danych usera z UserComponent. To jest idea dwukierunkowego serwisu w pigułce. W analogiczny sposób, ChildChildChildComponent da znać UserComponent, że odebrał dane, emitując wiadomość poprzez metodę tellSomethingToParent(), która zostanie wywołana na kliknięcie buttona.

PODSUMOWANIE

Idea bidirectional service jest bardzo przydatna, zwłaszcza jak chcemy wysyłać dane między komponentami, które nie są między sobą bezpośrednio połączone. Pamiętaj, że dodanie takiego serwisu do providers danego komponentu, tworzy Ci nową instancję tego serwisu. Jeśli planujesz użyć takiego serwisu, np. do wystawienia danych zalogowanego użytkownika w całej aplikacji, musisz ten serwis dodać do providers w głównym ngModule aplikacji, dzięki temu nasz serwis będzie singletonem (jedna instancja serwisu na całą aplikację).

Najczęściej zadawane pytania

Jak komunikować komponenty Angular, które nie są bezpośrednio połączone relacją rodzic-dziecko?

Komponenty Angular, które nie są bezpośrednio połączone relacją rodzic-dziecko, mogą wymieniać dane przez wspólny serwis. Serwis udostępnia strumienie, do których jeden komponent emituje wartości, a pozostałe komponenty wstrzykują serwis i subskrybują te strumienie. Przykładowo komponent może przekazać dane użytkownika do komponentu zagnieżdżonego kilka poziomów niżej.

Jak użyć Subject w serwisie Angular do przekazywania danych między komponentami?

Aby przekazywać dane między komponentami, utwórz w serwisie Subject, udostępnij go jako Observable i dodaj metodę emitującą wartości przez next(). W przykładzie userSource przechowuje Subject<User>, userContent$ udostępnia strumień przez asObservable(), a metoda shareUser() emituje użytkownika. Komponent odbierający dane subskrybuje userContent$.

Jak wysłać wiadomość z zagnieżdżonego komponentu do komponentu rodzica przez serwis?

Zagnieżdżony komponent może powiadomić komponent nadrzędny przez wspólny serwis z osobnym strumieniem wiadomości. W przykładzie ChildChildChildComponent wywołuje tellSomethingToParent(), która emituje tekst przez messageSource.next(message). Komponent nadrzędny może subskrybować udostępnione messageSent$ i odebrać wiadomość bez przekazywania jej przez każdy poziom drzewa.

Gdzie dodać serwis Angular, aby był współdzielony tylko w obrębie drzewa komponentów albo całej aplikacji?

Dodanie serwisu do providers komponentu tworzy jego instancję dostępną w obrębie tego komponentu i jego drzewa potomków. Komponenty spoza tego drzewa nie korzystają z tej samej instancji. Jeśli serwis ma być współdzielony w całej aplikacji, artykuł zaleca dodanie go do providers głównego NgModule, co zapewnia jedną instancję.

Czy trzeba wywoływać unsubscribe() po subskrypcji serwisu w komponencie Angular?

Wywołaj unsubscribe() w ngOnDestroy() dla subskrypcji, których komponent nie powinien utrzymywać po zniszczeniu. Przykład przechowuje subskrypcję w polu typu Subscription i zwalnia ją w ngOnDestroy(). W opisanym przykładzie nie jest to konieczne ze względu na cykl życia komponentów, ale autor zaleca pamiętać o tym w bardziej złożonych aplikacjach.

Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.