Ciężko nie zauważyć, że budowanie aplikacji w Angular, łączy się z ciągłą pracą w bibliotece RxJS. Stwierdziłem, że od czasu do czasu, skrobnę art o strumieniach. Myślę również o jakimś artykule typu RxJS w pigułce, dla początkujących developerów Angulara. W pierwszym wpisie o RxJS, zapraszam Cię do zapoznania się z operatorem share().
Grupy operatorów
Zanim przejdę do analizy operatora share, przypomnimy sobie ogólny podział operatorów wraz z przykładami:
- Filtrowanie: filter, first, last, debounce, skipUntil…
- Transformowanie: map, switchMap, scan, pluck…
- Obsługa błędów: catchError, retry…
- Tzw. utilsy, o różnym przeznaczeniu: tap, finally, toPromise…
- Multicasting: publish, share, shareReplay…
Część operatorów jest dużo bardziej popularna od pozostałych, chociażby ciężko spotkać aplikację angularową, w której nie został wykorzystany operator map lub catchError. Być może o grupie multicasting słyszysz po raz pierwszy. Jeśli tak, to już spieszę się z wyjaśnieniem ;).
Operatory z grupy multicasting, pozwalają między innymi na współdzielenie efektów ubocznych przez wielu subskrybentów, konwertują również observable ze stanu cold na hot przy spełnieniu określonych warunków (domyślnie observable są cold z pewnymi wyjątkami, np. fromEvent(input, 'click’), który jest hot – polecam poczytać artykuł na ten temat – cold vs hot observables). Najłatwiej będzie nam to zrozumieć na przykładzie operatora share.
Share operator
Share pozwala na współdzielenie zasobów przez wielu subskrybentów.
Zobaczmy prosty przykład:
W powyższym przykładzie jest oczywiste, że nasz side-effect w postaci loga ’I want to be called only once’ wyświetli się dwa razy i faktycznie tak się dzieje, no bo przecież mamy użyte 2x subscribe na jednym strumieniu. Może być niefajnie, jeśli byłaby to bardziej znacząca funkcja z określoną logiką.
Załóżmy, że chcemy wyłącznie raz wywołać side-effect, mimo wielu subskrypcji. I tu właśnie z pomocą przychodzi operator share. Share zwraca nowy strumień, które współdzieli oryginalny strumień wejściowy. Strumień będzie współdzielony tak długo jak:
- liczba subskrybentów będzie większa od 0
- observable nie są „completed”
Jeśli spełnione są powyższe warunki, to strumień z operatorem share jest hot.
Zastosujmy zatem operator share i sprawdźmy, czy log wywoła się tylko raz:
Faktycznie, dzięki dodaniu operatora share:
1 2 |
this.user$ = this.http.get('https://jsonplaceholder.typicode.com/users/1') .pipe(tap(() => this.log()), share()); |
Nasz side-effect w postaci loga w operatorze tap, wywołał się wyłącznie raz.
Share i kod synchroniczny
Wyżej wspomniałem, że share będzie działał, tak długo jak subskrypcje nie są „completed”. Nasz log wywołał się raz, ponieważ nasz kod jest asynchroniczny. W momencie drugiego subscribe(), który odpalił się synchronicznie po pierwszym subscribe(), to pierwsza subskrypcja nie była jeszcze skompletowana (http.get nieco trwał).
Do czego dążę – jeśli Twój kod będzie w pełni synchroniczny, to share() nie zadziała w takiej postaci w jakiej byś przywidywał. Rozpatrzmy przykład:
Zamiast strzała do API, zastosowałem funkcję of, który zwraca mi observable synchronicznie. W momencie drugiej subskrypcji, pierwsza już się skompletowała (cały kod jest synchroniczny) i nie jest już zapewniony warunek, że jakakolwiek subskrypcja nie jest jeszcze „completed”. Stąd log z tap pojawił się dwa razy. W takiej sytuacji, musiałbym użyć operatora shareReplay() zamiast share(), aby log pojawił się wyłącznie raz.
Realne zastosowanie
W pracy korzystam z operatora share najczęściej w sytuacjach, gdzie na jednym widoku korzystam z wielu słowników, które dostarczają mi z backendu wartości np. do selectów w formularzach. Wygląda to mniej więcej tak:
1 2 3 4 5 6 7 |
dictionaries = this.dictionariesService .getDictionaries([Dictionaries.POKEMONS, Dictionaries.HEROES, Dictionaries.CATS]) .pipe(share()); pokemons$ = this.dictionaries.pipe(map((data) => data[Dictionaries.POKEMONS])); heroes$ = this.dictionaries.pipe(map((data) => data[Dictionaries.HEROES])); cats$ = this.dictionaries.pipe(map((data) => data[Dictionaries.CATS])); |
Pingback: RxJS w Angular – co wypada wiedzieć – Angular.love
Wincyj, wincyj! 😉
> .getDictionaries([Dictionaries.POKEMONS, Dictionaries.HEROES, Dictionaries.CATS])
Hmmm, Pokemons? Pracujesz w Nintendo! 😀