Po ogłoszeniu przez team Angulara wprowadzenia sygnałów w Angularze 16 całe community mówi tylko o tym. Przy takim zainteresowaniu i naszym “zauroczeniu” w sygnałach możemy pomyśleć, że Angular idzie w stronę całkowitego usunięcia RxJs’a.
W tym artykule postaram się wyjaśnić dlaczego RxJs NIE zostanie całkowicie zastąpiony przez sygnały (przynajmniej nie w tej chwili 😉 ).
Wpływ sygnałów na obecny development
Sygnały to nowy reactive primitive wprowadzony do Angulara w wersji 16. Wcześniej mogliśmy korzystać z sygnałów chociażby w Solid.js, więc koncept jest dość dobrze znany. Więcej informacji o samych sygnałach możecie znaleźć w tym artykule.
Sygnały bez dwóch zdań zmienią podejście do pisania kodu, change detection i poprawią wydajność naszych aplikacji. Biorąc pod uwagę nature sygnałów i ich synchroniczny model przepływu danych, posiadają pewne ograniczenia, które idealnie uzupełnia RxJs.
Signals vs RxJs
RxJs może być asynchroniczny. Co to oznacza? RxJs może wykonywać wiele operacji w tym samym czasie, niezależnie od siebie. Idealnym przykładem na udowodnienie tego jest przykład przedstawiony przez Macieja podczas Angular.love Meetup.
W tym przypadku możemy zauważyć, że zmienna data
może nie mieć przypisanej wartości w momencie przypisywania jej do zmiennej someValue
, ponieważ dane cały czas są pobierane i subskrypcja jeszcze się nie wykonała.
Sygnały natomiast są synchroniczne. Czyli wykonują operacje w kolejności. Zdaje to egzamin jeśli chodzi o store’owanie danych lub wykorzystanie w template.
Dlaczego uważam, że sygnały nie zastąpią RxJs’a w całości?
Angular dostarcza nam API umożliwiające konwersję sygnałów do observable oraz observable do sygnału. W mojej opinii jest to jasne stwierdzenie, że oba te koncepty należy ze sobą łączyć i wykorzystywać ich różne możliwości. Poniżej przykłady jak wygląda taka konwersja:
1 2 3 4 5 6 7 8 9 10 11 |
step = signal('create'); step$ = toObservable(this.step); ngOnInit() { this.step$.subscribe((step) => { console.log(step); }); } users$ = of([{ id: 1, name: 'John Smith' }]); users = toSignal(this.users$); |
Wszyscy wiemy w czym sygnały są lepsze od RxJs’a. Ale w czym lepszy jest RxJs od sygnałów?
Przede wszystkim obsługa eventów z różnych źródeł, które mogą być mapowane, filtrowane i przekształcane w różnoraki sposób. W przypadku sygnałów taka obsługa również jest możliwa ale może być mniej czytelna i bardziej skomplikowana. Ogromna ilość rxjs’owych operatorów daje nam duże pole do popisu.
Dobrym i zarazem klasycznym przykładem gdzie użycie rxjs jest prostsze jest nasłuchiwanie na zmiany w inpucie przy jednoczesnym opóźnieniu wykonania późniejszych operacji.
Tak wyglądał by ten kod przy użyciu RxJs:
1 2 3 4 5 |
this.control.valueChanges.pipe( debounceTime(300), ).subscribe(value => { this.search.emit(value) }) |
Natomiast przy użyciu samych sygnałów kod wyglądał by w ten sposób:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import {Component, effect, signal} from '@angular/core'; @Component({ selector: 'app-root', template: ` <input [ngModel]="searchValue()" (ngModelChange)="searchValue.set($event)"> `, styleUrls: ['./app.component.scss'] }) export class AppComponent { searchValue = signal(''); debouncedSearchValue = signal(this.searchValue()); constructor() { let timeoutId: ReturnType<typeof setTimeout> | undefined; effect((onCleanup) => { const search = this.searchValue(); timeoutId = setTimeout(() => { this.debouncedSearchValue.set(search); }, 500); onCleanup(() => { clearTimeout(timeoutId); }); }); effect(() => { console.log(this.debouncedSearchValue()); }); } } |
Według mnie, kod RxJs’owy wygląda lepiej i z pewnością jest bardziej czytelny dla developera. Pamiętajmy również, że ten kod możemy napisać z wykorzystaniem sygnałów i RxJs co da nam jeszcze więcej benefitów niż wybranie tylko jednego podejścia.
Ogólnie mówiąc, handlowanie asynchronicznych eventów za pomocą sygnałów jest po prostu trudne i czasami nie opłacalne. RxJs posiada mnóstwo operatorów, które rozwiązują te problemy za nas. Ponadto RxJs istnieje od lat, jest wykorzystywany w ogromnej ilości projektów i dzięki temu jest bardzo niezawodny i przetestowany.
Na temat przepisania i dostosowania RxJs’owych operatorów do sygnałów wypowiadał się na Twitterze Michael Hladky. Według niego nie jest to najbardziej wydajne podejście.
Network request. Żądania XHR są asynchroniczne. Observable pozwala nam na handlowanie ich, przekształcanie, obsługę eventową takich jak success, error i completion. Za pomocą operatorów RxJs możemy również ponawiać request oraz je anulować na co nie pozwalają nam sygnały. W samych sygnałach nie znajdziemy np. Klasycznego catchErrora czy switcthMapy, która anuluje request trwający w danej chwili i wykonuje nowy, zazwyczaj z nowymi danymi.
Dodatkowym, jednym z filarów Angulara jest kompatybilność wsteczna. Całkowite usunięcie RxJs’a zmusi developerów do przeznaczenia niesamowitej ilości godzin do refactoru, który nie będzie ani prosty ani przyjemny. W przypadku wielu projektów taka zmiana oznaczała by przepisanie projektu na nowo.
Podsumowując
Bez dwóch zdań, sygnały to rewolucja jeśli chodzi o wydajność w Angular i podejście do tworzenia aplikacji. RxJs jest potężnym narzędziem przy pomocy którego jesteśmy w stanie łatwo handlować asynchroniczne operacje. Moim zdaniem jednak sygnały nie zastąpią całkowicie RxJs’a. Z tych dwóch narzędzi należy korzystać jednocześnie podczas tworzenia naszych aplikacji. To zapewni nam dobrą wydajność naszej aplikacji ale również dobry developer experience.
Dodaj komentarz