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);