87 RX – Creiamo le interfacce typeScript

Adesso vediamo come creare delle interfacce con typeScript che possono aiutare nell’autocomplete e nella validazione dei dati delle api di google.

Guardando i dati che google ci restituisce nella console, notiamo che abbiamo :

un array di items
un parametro chiamato kind che ha valore volumes (le api di google books hanno diversi tipi, nel nostro esempio sono volumes)
La proprietà totalItems che ci restituisce il numero di libri trovati dalla chiamata

Ecco la prima interfaccia:

interface GoogleBook {
    totalItems: number
    kind: string
    items: []
}

L’altra interfaccia che andremo a creare è quella per mappare le proprietà del parametro volumeInfo, quindi dalla console le copiamo tutte e andiamo a cancellare quelle che non ci servono

interface VolumeInfo {
    authors: []
    description: string
    imageLinks: BookThumbnails
    infoLink: string
    language: string
    previewLink: string
    title: string
}

Da notare che l’oggetto imageLinks ha a sua volta altre proprietà, per cui creo un interfaccia separata BookThumbnails con le proprietà corrispondenti, per questo ho assegnato a imageLinks il tipo BookThumbnails

interface BookThumbnails {
    smallThumbnail: string
    thumbnail: string
}

Utilizziamo RxJs con TypeScript

Grazie all’integrazione lato browser, abbiamo già disponibile l’oggetto rxjs

https://unpkg.com/rxjs/bundles/rxjs.umd.min.js

Potremmo anche installare i moduli nella cartella e usarlo con node, ma per questo esempio continuiamo così. Il fatto è che però rxjs non potrà essere utilizzato direttamente in typeScript perchè quest’ultimo non ha conoscenza di questo oggetto. Effettivamente se noi comunque inseriamo le dichiarazioni degli operatori e del from, il nostro IDE segnalerà errore in typeScript, però nel momento della transpilazione in javaScript, il tutto funzionerà comunque, proviamo a inserirlo nella funzione getBooks() del file .ts

function getBooks(booktitle: string) {
    const { from } = rxjs;
    const { map, switchMap, tap, } = rxjs.operators;

    let apiurl = 'https://www.googleapis.com/books/v1/volumes?q=';

    const p = fetch(apiurl + booktitle).then(res => res.json());
        //.then( books => console.log(books) );
    from(p).subscribe( data => console.log(data));
}

La soluzione per evitare la segnalazione di errore è quella di utilizzare il declare di typeScript dichiarando la costante rxjs che sarà di tipo any. Potrei anche dichiararlo come oggetto, ma a quel punto, riceverò poi successivi errori per la mancanza dei vari operatori non specificai come metodi (rxjs.map)

declare const rxjs: any;

a questo punto dovrò specificare che la funzione data della subscribe è di tipo any, avvolgendola tra parentesi tonde

from(p).subscribe( (data:any) => console.log(data));

aggiungiamo un tap nel from che stampa una stringa per verificarne il funzionamento nel browser

from(p)
    .pipe(
        tap( (ele: any) => console.log('tap'))
    )
    .subscribe( (data:any) => console.log(data));

Sfruttando le interface, possiamo dire che data della subscribe è di tipo GoogleBook, mentre nel console data è di tipo items

subscribe( (data:GoogleBook) => console.log(data.items));

adesso avremo in console stampati solo gli items.

Ora dobbiamo trasformare i dati items in uno stream di dati utilizzando l’operatore switchMap che riceverà i dati di tipo GoogleBook prendendoli col from dagli elementi items

switchMap( (data:GoogleBook) => from(data.items))

a questo punto possiamo aggiungere un0 interface BookItem che conterrà volumeInfo e id

interface BookItem {
    volumeInfo: VolumeInfo
    id: string
}

a questo punto tap deve ritornare un elemento di tipo BookItem e stampare un elemento volumeInfo e togliamo il console della subscribe facendo ritornare solo i dati.
Ecco la from completa

from(p)
    .pipe(
        switchMap( (data:GoogleBook) => from(data.items)),
        tap( (ele: BookItem) => console.log(ele.volumeInfo))
    )
    .subscribe( (data:GoogleBook) => data);

Ora che vengono mostrati tutti i dati, vogliamo filtrare mostrando solo alcuni elementi, in quanto il tap non agisce sullo stream, quindi dobbiamo utilizzare il map per filtrarli, creeremo quindi un oggetto di che conterrà solo i parametri che ci servono. Questo oggetto diremo però che è di tipo Book, interfaccia che andremo a creare

interface Book {
    title: string
    description: string
    authors: []
    categories: []
    thumbnail: string
}

creiamo l’oggetto book di tipo Book con le proprietà che vogliamo mostrare inserendolo nel map

map( (ele: BookItem) => {
    const book:Book = {
        title : ele.volumeInfo.title,
        categories : ele.volumeInfo.categories,
        authors : ele.volumeInfo.authors,
        description : ele.volumeInfo.description,
        thumbnail : ele.volumeInfo.imageLinks.thumbnail
    };
return book;

da notare che essendo andato a capo nell’ arrow function, ho dovuto inserire le graffe e obbligatoriamente restituire il book col return.

Spostiamo il tap in fondo dopo il map e modifichiamo gli elementi che riceve con book e facciamo il console.log di book

tap( (book:Book) => console.log(book)),

ecco la from completa

from(p)
        .pipe(
            switchMap( (data:GoogleBook) => from(data.items)),
            map( (ele: BookItem) => {
                const book:Book = {
                    title : ele.volumeInfo.title,
                    categories : ele.volumeInfo.categories,
                    authors : ele.volumeInfo.authors,
                    description : ele.volumeInfo.description,
                    thumbnail : ele.volumeInfo.imageLinks.thumbnail
                };
                return book;
            }
        ),
            tap( (book:Book) => console.log(book)),
        )
        .subscribe( (data:Book) => data);