Aurelia.io #3 – Fetch-client

Cześć,

dziś trzecia część podstaw frameworka Aurelia.io. Jeżeli nie czytaliście poprzednich wpisów z tej serii, zachęcam do zapoznania się z nimi. Link macie tu i tu 😉 Dziś zajmiemy się komunikacją naszej aplikacji z serwerem. Do tego celu wykorzystamy paczkę o nazwie aurelia-fetch-client. Aby ją zainstalować wystarczy, że z poziomu root naszej aplikacji wpiszemy w konsoli:

 

npm install aurelia-fetch-client

 

Po kilku sekundach paczka powinna się pobrać, a my możemy przejść do działania. Co więc dziś zaimplementujemy? Naszym celem będzie pobranie danych książki na podstawie id. W tym celu skorzystam ze starego API, które napisałem kilka miesięcy temu. Udostępnia ono metodę, która będzie nas interesowała:

 

[GET]  {host}/api/Books/{bookId}

 

Przejdźmy więc do implementacji. Piszemy ViewModel. Ma on następującą postać:

 

import {HttpClient} from 'aurelia-fetch-client';
import {inject} from 'aurelia-framework';

@inject(HttpClient)
export class FetchViewModel {
    bookId: number;
    book: any;

    private httpClient: HttpClient;

    constructor(httpClient: HttpClient) {
        this.httpClient = httpClient;

        this.httpClient.configure(config => {
            config.withBaseUrl('http://localhost:64857/');
            config.withDefaults({ mode: 'cors' });
        });
    }

    getBookById() {
        let url = `api/Books/${this.bookId}`;

        this.httpClient.fetch(url, {}).then(result => result.json()).then((result: any) => {
            this.book = result.book;
        });
    }
}

 

Pierwszą rzeczą jaką zrobimy to import klienta HTTP z pobranej paczki. Aby dostarczyć instancję ów klienta do naszego ViewModelu musimy skorzystać z wbudowanego we framework wstrzykiwania zależności. Jeżeli to pojęcie nie jest Wam znane, odsyłam do bloga Maćka Aniserowicza, który dokładnie to opisał. Link macie tu. Idąc dalej widzimy oznaczenie klas, które mają zostać wstrzyknięte w konstruktorze. W naszym przypadku w środku @inject pojawia się jedynie HttpClient. W konstruktorze FetchViewModel wstrzykniętą instancję przypisujemy do prywatnego pola httpClient. Następnie przechodzimy do konfiguracji metodą configure. Ogranicza się ona jedynie do dwóch rzeczy:

  • konfiguracja hosta aplikacji serwerowej poprzez metodę withBaseUrl
  • ustawieniem trybu. My będziemy korzystać z CORS. W tym przypadku ten zapis nie jest obligatoryjny, ponieważ CORS jest domyślnym trybem, jednak chciałem zwrócić Waszą uwagę, na miejsce gdzie należy to konfigurować. Pamiętajcie także o włączeniu CORS na serwerze. Tu przykład dla ASP.NET Web API 2.

 

Oprócz tego nasz ViewModel posiada jedną metodę – getBookById. Robi ona dwie rzeczy. Po pierwsze tworzy ścieżkę pod którą będziemy wysyłać nasz żądanie. Jak widzicie, nie posiada ona nazwy hosta. W trakcie wykonywania żądania HttpClient dokona konkatenacji obu fragmentów .  Korzystamy z dobrodziejstw TypeScript, aby w schludny sposób zbudować ścieżkę, po czym przechodzimy do wykonania żądania. W tym celu musimy wywołać metodę fetch. Pierwszym parametrem jest ścieżka, pod którą żądanie zostanie wysłane, a drugim obiekt konfiguracyjny w którym możemy określić m.in. typ żądania (GET, PUT itd.), body, oraz nagłówki. Ponieważ domyślnie wysyłany request to GET, a my nie potrzebujemy żadnych tokenów, aby ubiegać się o zasoby serwera, możemy umieścić w metodzie pusty obiekt.  Metoda fetch zwraca obiekt Promise. W największym uproszczeniu możemy powiedzieć, że reprezentuje on obietnicę wykonania naszego żądania przez serwer. Z racji tego, że request jest asynchroniczny, nie jesteśmy w stanie stwierdzić czy wszystko powiedzie się zgodnie z naszymi oczekiwaniami. Z tego względu musimy zaimplementować „na zaś” co nasza aplikacja ma zrobić w przypadku gdy żądanie zostanie wykonane poprawnie (zwrócony zostanie kod HTTP 200), a co jeśli nie. My zakładamy tylko pozytywny przypadek 😀 Używamy dwóch metod rozszerzających then. Pierwsza z nich deserializuje rezultat zwrócony przez serwer w postaci JSON. Druga metoda przypisuje rezultat typu any (nie chciało mi się pisać klasy na kliencie :D) do zmiennej book. Zobaczmy jak wygląda widok:

 

<template>
    <input type="number" value.bind="bookId" />
    <button click.trigger="getBookById()">Get book </button>
    <br>
    <hr>
    <div if.bind="book">
        <p>Title: ${book.title}</p>
        <p>Author: ${book.authorName} ${book.authorSurname}<p>
        <p>Description: ${book.description}<p>
        <p>ISO: ${book.iso}<p>
            <img src="http://localhost:64857/api/Graphic/${book.graphicId}" />
    </div>
</template>

 

Jak widać mamy input, w którym będziemy wpisywać id książki oraz przycisk wywołujący metodę getBookById. Po pobraniu danych powinniśmy zobaczyć klika informacji tj. tytuł, autor, opis, numer ISO oraz zdjęcie okładki. Zobaczmy jak to działa:

 

sample2

Jest ok. Analogicznie możemy wykonywać żądania typu np. POST. Wystarczy aby nasz obiekt konfiguracyjny w metodzie fetch wyglądał tak:

 


var requestConfig: any =
    {
        method: 'post',
        body: json(data)
    };

 

Czy to wszystko? Tak. Czy jest to ładne? Nooo nie do końca. Wychodzę z założenia, że tak jak kontrolery ASP.NET nie powinny realizować logiki biznesowej, tak ViewModel na kliencie nie powinien zajmować się obsługą HTTP. W związku z tym dokonamy enkapsulacji naszego HttpClient w serwis, który będzie dostarczał metodę pobrania książki. Kod wygląda następująco:

 


import {HttpClient} from 'aurelia-fetch-client';
import {inject} from 'aurelia-framework';

export interface IFetchService {
    getBookById(bookId: number): Promise<any>;
}

@inject(HttpClient)
export class FetchService implements IFetchService {
    private httpClient: HttpClient;

    constructor(httpClient: HttpClient) {
        this.httpClient = httpClient;

        this.httpClient.configure(config => {
            config.withBaseUrl('http://localhost:64857/');
        });
    }

    getBookById(bookId: number): Promise<any> {
        let url = `api/Books/${bookId}`;

        return this.httpClient.fetch(url, {}).then(result => result.json());
    }

}

 

Teraz wystarczy, aby nasz ViewModel zamiast HttpClient miał wstrzykiwany FetchService:

 


import {HttpClient} from 'aurelia-fetch-client';
import {inject} from 'aurelia-framework';

import service = require('fetch-service');

@inject(service.FetchService)
export class FetchViewModel {
    bookId: number;
    book: any;

    private fetchService: service.IFetchService;

    constructor(fetchService: service.FetchService) {
        this.fetchService = fetchService;
    }

    getBookById() {
        this.fetchService.getBookById(this.bookId).then((result: any) => {
            this.book = result.book;
        });
    }
}

 

Kod wygląda teraz dużo lepiej, ponieważ całą konfigurację klienta HTTP „zrzuciliśmy” na serwis. To rozwiązanie jednak też nie jest perfekcyjne 😀 Warto pokusić się jeszcze o abstrakcję, która zajmie się konfiguracją lub dołączeniem np. nagłówków do żądań. Przykład takiego serwisu bazowego przedstawiłem w jednym z moich postów konkursowych. Link macie tu 🙂

 

Na dziś to chyba wszystko! Moim zdaniem trzy wpisy z tej serii pozwolą Wam na stworzenie fajnie działających aplikacji. Reszta to dodatki. Kolejny post dotyczyć będzie bundlingu, jednak nie będę znów obiecywał kiedy się pojawi bo mam problem z dotrzymywaniem terminów 😀 Zachęcam Was do śledzenia mnie na twitterze oraz facebooku gdzie wrzucam najnowsze wpisy 😉

Stay in touch !

 

You may also like...