Observables – co to jest? I jak używać ich w kodzie
Wprowadzenie
Observables są istotną częścią frameworka Angular, która zapewnia potężny sposób obsługi asynchronicznych strumieni danych. W tym artykule zbadamy, czym są obserwowalne, dlaczego są ważne i jak można je wykorzystać w kodzie Angular. Przedstawimy również przykłady kodu, aby zilustrować ich użycie i zademonstrować ich zalety.
Zrozumienie Observables
Observables są reprezentacją strumienia danych, które mogą zmieniać się w czasie. Są one podstawową częścią biblioteki Reactive Extensions for JavaScript (RxJS), która jest szeroko stosowana w aplikacjach Angular. Observables mogą emitować wiele wartości w czasie i mogą być obserwowane przez wielu subskrybentów. Podążają za wzorcem projektowym obserwatora, w którym Observable jest producentem wartości, a obserwatorzy (subskrybenci) reagują na te wartości.
Tworzenie Observables
Aby utworzyć observable, można użyć klasy Observable dostarczanej przez RxJS. Oto przykład tworzenia prostego observable, który emituje sekwencję liczb:
import { Observable } from 'rxjs';
const numberObservable = new Observable<number>((observer) => {
let count = 0;
const intervalId = setInterval(() => {
observer.next(count++);
}, 1000);
return () => {
clearInterval(intervalId);
};
});
W tym przykładzie tworzymy observable, który emituje liczbę co sekundę. Konstruktor observable przyjmuje funkcję, która definiuje sposób, w jaki observable będzie generował wartości. W tym przypadku funkcja otrzymuje obiekt observer i używa metody next do emitowania wartości.
Subskrybowanie Observables
Po utworzeniu observable można go zasubskrybować, aby zacząć otrzymywać emitowane przez niego wartości. Oto jak można zasubskrybować utworzony wcześniej obiekt numberObservable:
numberObservable.subscribe((value) => {
console.log(value);
});
W tym przykładzie subskrybujemy numberObservable i udostępniamy funkcję zwrotną do obsługi emitowanych wartości. Za każdym razem, gdy emitowana jest nowa wartość, wywoływana jest funkcja zwrotna z tą wartością.
Operators i Transformation
Observables zapewniają szeroki zakres operatorów, które umożliwiają przekształcanie, filtrowanie, łączenie i manipulowanie emitowanymi wartościami. Przyjrzyjmy się przykładowi wykorzystującemu operator map do przekształcenia emitowanych liczb w ich kwadraty:
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
const squaredObservable = numberObservable.pipe(
map((value) => value * value)
);
squaredObservable.subscribe((value) => {
console.log(value);
});
W tym przykładzie użyliśmy metody pipe do połączenia operatora map z numberObservable. Operator map przekształca każdą emitowaną wartość, mnożąc ją przez siebie. W rezultacie otrzymujemy wartości podniesione do kwadratu.
Filtrowanie Observables
Oprócz transformacji, observables umożliwiają filtrowanie emitowanych wartości na podstawie określonych warunków. Operator filtrowania jest powszechnie używany do tego celu. Rozważmy przykład, w którym odfiltrowujemy liczby parzyste z numberObservable:
import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
const oddNumberObservable = numberObservable.pipe(
filter((value) => value % 2 !== 0)
);
oddNumberObservable.subscribe((value) => {
console.log(value);
});
W tym przykładzie użyliśmy operatora filtru, aby zezwolić tylko liczbom nieparzystym na przejście przez observable. Funkcja wywołania zwrotnego wewnątrz operatora filtru sprawdza, czy wartość nie jest podzielna przez 2 (tj. liczba nieparzysta) i emituje tylko takie wartości.
Obsługa błędów
Observables zapewniają również mechanizmy obsługi błędów emitowanych podczas strumienia. Operator catchError jest powszechnie używany do wychwytywania błędów i ich obsługi. Rozważmy przykład, w którym błąd jest celowo wyrzucany po spełnieniu określonego warunku:
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
const errorObservable = new Observable<number>((observer) => {
let count = 0;
const intervalId = setInterval(() => {
if (count === 3) {
observer.error('Something went wrong!');
} else {
observer.next(count++);
}
}, 1000);
return () => {
clearInterval(intervalId);
};
});
errorObservable.pipe(
catchError((error) => {
console.log('Error:', error);
return throwError('Error occurred');
})
).subscribe({
next: (value) => {
console.log(value);
},
error: (error) => {
console.log('Handled Error:', error);
},
});
W tym przykładzie celowo rzuciliśmy błąd, gdy liczba osiągnie 3. Operator catchError wychwytuje błąd, rejestruje go i emituje nowy błąd za pomocą throwError. Subskrybent zapewnia oddzielne procedury obsługi „next” i „error” do obsługi odpowiednio emitowanych wartości i błędów.
Rezygnacja z subskrypcji i czyszczenie
Observables pozwalają na łatwe czyszczenie, zapewniając sposób na wypisanie się ze strumienia. Gdy subskrybujesz observables, metoda subskrypcji zwraca obiekt subskrypcji, który może być użyty do anulowania subskrypcji. Oto przykład:
const subscription = numberObservable.subscribe((value) => {
console.log(value);
});
// Unsubscribe after 5 seconds
setTimeout(() => {
subscription.unsubscribe();
}, 5000);
W tym przykładzie przechowujemy obiekt subskrypcji w zmiennej, a następnie wywołujemy jego metodę anulowania subskrypcji po 5 sekundach. Gwarantuje to, że przestaniemy otrzymywać wartości z observable i wyczyścimy wszelkie zasoby z nim powiązane.
Testowanie Observables
Właściwym podejściem do testowania observables jest użycie funkcji fakeAsync dostarczanej przez pakiet @angular/core/testing. Ta funkcja narzędziowa umożliwia pisanie synchronicznie wyglądających testów dla kodu asynchronicznego, w tym observables.
Aby zademonstrować, jak używać fakeAsync do testowania observables, rozważmy prosty przykład, w którym mamy usługę zwracającą observable. Chcemy sprawdzić, czy observable emituje oczekiwane wartości.
Po pierwsze, załóżmy, że mamy usługę o nazwie DataService, która udostępnia observable:
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable()
export class DataService {
getData(): Observable<number> {
return of(1, 2, 3, 4, 5);
}
}
Teraz napiszmy test używający fakeAsync, aby sprawdzić, czy observable emituje oczekiwane wartości:
import { fakeAsync, tick } from '@angular/core/testing';
import { DataService } from './data.service';
describe('DataService', () => {
let service: DataService;
beforeEach(() => {
service = new DataService();
});
it('should emit values from the observable', fakeAsync(() => {
let result: number[] = [];
service.getData().subscribe((value: number) => {
result.push(value);
});
tick(); // Simulate the passage of time
expect(result).toEqual([1, 2, 3, 4, 5]);
}));
});
W powyższym teście użyliśmy funkcji fakeAsync do napisania naszego kodu testowego. W bloku fakeAsync zasubskrybowaliśmy observable zwracane przez getData() i zapisaliśmy emitowane wartości w tablicy wyników. Następnie użyliśmy funkcji tick, aby opróżnić wszystkie oczekujące mikrozadania i przyspieszyć wirtualny zegar. Po funkcji tick możemy wykonać asercje na tablicy wyników, aby upewnić się, że observable wyemitował oczekiwane wartości. Używając fakeAsync, możemy pisać czyste i synchronicznie wyglądające testy dla asynchronicznych observables. Zapewnia to wygodny sposób kontrolowania czasu i obsługi operacji asynchronicznych w deterministyczny sposób.
Wnioski
Observables są potężnym narzędziem do obsługi asynchronicznych strumieni danych w aplikacjach Angular. Zapewniają one elastyczne i reaktywne podejście do zarządzania i przekształcania danych. Rozumiejąc koncepcje stojące za observables i wykorzystując bibliotekę RxJS, można tworzyć bardziej responsywny i wydajny kod. Włączenie observables do projektów Angular zwiększy twoją zdolność do obsługi operacji asynchronicznych i tworzenia solidnych aplikacji.
Pamiętaj, aby zaimportować niezbędne operatory RxJS i zapoznać się z ogromną kolekcją dostępnych operatorów, aby dostosować zachowanie obiektów observable. Dzięki observables możesz w pełni wykorzystać zasady programowania reaktywnego i odblokować prawdziwy potencjał asynchronicznej obsługi danych w Angular. Miłego kodowania!
Bibliografia
- https://rxjs.dev/guide/observable
- https://angular.io/guide/observables
- https://luukgruijs.medium.com/understanding-creating-and-subscribing-to-observables-in-angular-426dbf0b04a3
- https://codecraft.tv/courses/angular/unit-testing/asynchronous/
- https://angular.io/api/core/testing/tick
- https://angular.io/api/core/testing/fakeAsync