Javascript

Kurs Angular 2 – Warstwa serwisów, moduł HTTP

W poprzednim artykule omówiłem podstawowe informacje dotyczące serwisów – w jaki sposób je tworzyć, gdzie i kiedy stosować. Dzisiaj rozszerzymy te zagadnienia o stworzenie serwisu komunikującego się z zewnętrznym API, wykorzystując dostarczony przez twórców Angular 2 HttpModule.

HttpModule i JsonpModule

Moduły HttpModule i JsonpModule udostępniają nam gotowe serwisy – odpowiednio Http i Jsonp, które możemy swobodnie wstrzyknąć przez konstruktor naszego serwisu, dzięki czemu tworzenie aplikacji komunikujących się z zewnętrznym serwerem w Angular 2 jest bardzo wygodne i wymaga od nas minimalnego nakładu pracy.

Aby zaprezentować działanie serwisu Http, wykorzystamy kod z poprzedniego artykułu, w którym zastąpimy stały zbiór danych zwracany przez serwis na rezultat zapytania GET z zewnętrznego API. Dodatkowo dorzucimy możliwość dodania nowego zadania do listy przy pomocy metody POST.

Uwaga! Zanim będziemy mogli korzystać z serwisów udostępnianych przez moduły, musimy je wcześniej dopisać do listy imports modułu – w naszym przypadku jest to AppModule i HttpModule już się na niej znajduje.

Pobieramy dane z API

Rozpoczniemy od udoskonalenia naszego TodosService znajdującego się w pliku todos.service.ts. W pierwszej kolejności musimy wstrzyknąć zależność przez konstruktor:


constructor(private http: Http) {}

Następnie zastąpimy obecną metodę getTodos() zwracającą stałe dane na metodę wykorzystującą klasę Http (założyłem, że nasz serwer zewnętrzny znajduje się pod adresem http://localhost:3030/):


getTodos(): Observable<Todo[]> {
  return this.http.get("http://localhost:3030/users/")
              .map(this.extractData)
              .catch(this.handleError);
}

Szybko możemy zauważyć, że metoda get() (jak i również pozostałe metody w przypadku obiektu klasy Http) zwracają obiekt typu Observable. Dodatkowo wykorzystaliśmy dwie metody pomocnicze wprost z oficjalnej dokumentacji extractData() i handleError(), których ciała umieściłem poniżej:


private extractData(res: Response) {
  const body = res.json();
  return body.data || { };
}

private handleError (error: Response | any) {
  let errMsg: string;

  if (error instanceof Response) {
    const body = error.json() || '';
    const err = body.error || JSON.stringify(body);
    errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
  }
  else {
    errMsg = error.message ? error.message : error.toString();
  }
  return Observable.throw(errMsg);
}

Ich przeznaczenie wydaje się być zrozumiałe.

W ramach naszego TodosService to już wszystkie modyfikacje, jakich musieliśmy dokonać. Przenosimy się do naszego AppComponent (znajdującego się w pliku app.component.ts) aby wykorzystać zwracany z poziomu serwisu obiekt typu Observable. Musimy przekształcić dotychczasowy kod konstruktora oraz zaimplementować dwa interfejsy OnInit oraz OnDestroy:


export class AppComponent implements OnInit, OnDestroy {
  public todos: Todo[];
  private todos$: Subscription;

  constructor(private todosService: TodosService) {}

  ngOnInit() {
    this.todos$ = this.todosService.getTodos()
          .subscribe((todos) => this.todos = todos);
  }

  ngOnDestroy() {
    if(this.todos$) this.todos$.unsubscribe();
  }

  ...

}

Jak możemy zauważyć, subskrybujemy zwracany obiekt Observable, dzięki czemu w przypadku uzyskania wartości, będziemy mogli uzupełnić tablicę przechowywaną w klasie komponentu. Dodatkowo przechowujemy obiekt subskrypcji w zmiennej todos$, co pozwoli nam w momencie niszczenia instancji komponentu zwolnić subskrypcję (unikniemy ewentualnych wycieków pamięci).

Zapytania POST

Nasza aplikacja korzysta już z realnych danych zaciąganych z zewnętrznego serwisu. Teraz dorzucimy jej jeszcze jedną funkcjonalność – możliwość wysyłania nowych zadań przy pomocy metody POST.

Wracamy do naszego TodosService i dorzucamy metodę addTodo():


addTodo(todo: Todo) {
  const headers = new Headers({ 'Content-Type': 'application/json' });
  const options = new RequestOptions({ headers: headers });

  return this.http.post("http://localhost:3030/todos/", { todo }, options)
              .map(this.extractData)
              .toPromise()
              .catch(this.handleError);
}

Jak widzimy – budowanie zapytania typu POST jest równie proste. Warto zwrócić uwagę, że w tym przypadku zdecydowałem się dorzucić wywołanie metody toPromise(), dzięki czemu zwrócony zostanie nam obiekt typu Promise a nie Observable

Ponownie przenosimy się do AppComponent i zastępujemy poprzednią funkcję addTodo():

public addTodo(todo: Todo) {
  this.todosService.addTodo(todo)
    .then(() => this.todos.unshift(todo));
}

To wszystko!

Podsumowanie

W dzisiejszym artykule zaprezentowałem możliwości modułu dostarczonego przez zespół Angulara – HttpModule, dzięki czemu nasz TodosService stał się pełnoprawnym serwisem korzystającym z zewnętrznego API. W artykule nie prezentowałem działania Jsonp, ponieważ praca z nim jest niezwykle podobna.

Pełny kod źródłowy dostępny na GitHub.