Programowanie reaktywne

Programowanie reaktywne jest to programowanie asynchroniczne oparte na obserwacji strumienia danych. W pierwszej kolejności wykorzystywane jest do obsługi zapytań HTTP, ale również może obsługiwać zdarzenia, odczytywać elementy tablic, itp. Framework Angular2 wykorzystuje bibliotekę RxJs, która z kolei wykorzystuje obiekt Observable.
Observable porównać można do Promise, który również wykorzystywany jest do obsługi HTTP. Istnieje kilka różnic pomiędzy Observable, a Promise.

Observable vs Promise

OBSERVABLE PROMISE
Umożliwia pracę z wieloma wartościami Zwraca pojedynczą wartość
Można go anulować Nie można go anulować
Oferuje wiele rozszerzeń dzięki RxJs

RxJs – wybrane metody

Obiekt RxJs Observable może reprezentować wiele asynchronicznych strumieni z danymi, do którego to obiektu należy się zasubskrybować subscribe. Wcześniej jednak RxJs oferuje wiele metod, dzięki którym możemy manipulować strumieniem danych na wiele sposobów. Poniżej lista najciekawszych metod, a następnie przykłady użycia (Observable + metody + subscribe).

  • .filter((value:number) => value % 2 === 0 ) – filtruje dane przychodzące zgodnie ze wzorem (w tym wypadku przepuszcza liczby parzyste)
  • .delay(500) – opóźnia działanie
  • .startWith(0) – rozpoczyna od podanej wartości
  • .distinctUntilChanged() – wstrzymuje działanie do momentu, aż nie będzie zmiany
  • .timestamp() – dodaje indywidualny znacznik godzinowy
  • .last() – przepuści tylko ostatni element
  • .first() – tylko pierwszy element
  • .find(x => x < 5 ) – tylko pierwszy element, który spełni warunek
  • .elementAt(4) – element, którego pozycja została wskazana w nawiasie
  • .skip(3) – przeskakuje liczbę wskazanych pozycji począwszy od 1
  • .take(2) – pobiera liczbę wskazanych pozycji począwszy od 1
  • .max() – wskazuje max pod warunkiem końca strumienia
  • .min() – wskazuje min pod warunkiem końca strumienia
  • .every(x => x > 0) – typ boolean sprawdza czy wszystkie elementy spełniają warunek dla zakończonego strumienia
  • .average(function (x) { return x.value }) – wskazuje średnią wartość, ale typ Observable musi być liczbą
  • .toArray() – zwraca wynik jako tablicę
  • .debounceTime(2100) – czeka wskazany czas zanim zadziała (pomocne w opóźnianiu zapytań np. oczekiwanie na kilka kliknięć lub na szybkie wpisanie w input)
  • .flatMap(x => [x]) – spłaszcza zagnieżdżone observable do jednego płaskiego
  • .map(x => x.value + ‚ TS:’ + x.timestamp) – zamienia każdy z obiektów przychodzących przez zastosowanie funkcji (przykład powiązany z .timestamp)
  • .map(x => Number(x)+10 ) – drugi łatwiejszy przykład użycia często stosowanej metody .map

Przykłady w Angular2

Przy pierwszym kontakcie z dokumentacją RxJs możemy spotkać się z problemem, ponieważ w dokumentacji wskazany jest następujący sposób na dołączenie tej biblioteki import Rx from 'rxjs/Rx';. Jeżeli pojawi się błąd: has no default export, jego rozwiązaniem jest: import * as Rx from 'rxjs/Rx';


Pierwszy przykład:

ngOnInit() {
    let counter = 1;
    let stream = new Observable(observer => {
      setInterval(() => {
              observer.next(counter++);
          },1000);
      setInterval(() => {
          observer.complete(); //observer zostaje zakończony po wskazanym czasie
          },10100);            //w rezultacie otrzymuje 10 liczb od 1 do 10
    });
    stream
    .filter((value:number) => value > 2 )
    .map(x => Number(x)+10 )
    .subscribe(value => console.log(value));

W wyniku działania otrzymamy w konsoli liczby od 13 do 20 ponieważ wynik jednych metod wpływa na kolejne, które znajdują się poniżej.


Drugi przykład

W pliku nazwa.component.html umieszczamy pole input oraz reakcję na klawisz ENTER:

< input type="text" (keyup.enter)="addArray(temp)" [(ngModel)]="temp">

następnie w pliku nazwa.component.ts właściwy Observable

import { Component, OnInit } from '@angular/core';
import * as Rx from 'rxjs/Rx';
import { Observable } from 'rxjs';
export class NazwaComponent implements OnInit {
 constructor() {
   }
 table = [1, 4, 7, 8, 10];
 data;
 addArray = function(newData) {
    this.table.push(newData);
    this.data = Rx.Observable.from(this.table)
    .filter((value:number) => value % 2 === 0 )
    .delay(500) 
    .distinctUntilChanged() //wstrzymuje do momentu, aż nie będzie zmiany
    .subscribe(x => console.log(x));
  }

W rezultacie ze wskazanym opóźnieniem otrzymamy w konsoli liczby parzyste 4, 8, 10 oraz te, które zostały wpisane przez użytkownika i były parzyste.


Trzeci przykład

Najbardziej użyteczny przykład. Na jego bazie możemy robić zapytania typu GET.

import {Http, Response} from '@angular/http';

searchAlbums = function(url) {
    this.http.get(url)
    .subscribe((response:Response)=>{
       let data = response.json()
       console.log(data);
    })
};

Nie pojawiła się tutaj nazwa Observable tak jak w powyższych przykładach, jednak wystarczy w Visual Code Studio najechać myszą na instrukcję get i zobaczymy, że tutaj również mamy do czynienia z Observable.

RxJs - Observable