Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Angular 2 Http, API e Servizi asincroni

Come funziona il servizio Http di Angular 2 e il pattern Observable preferibile alle Promise per gestire le richieste asincrone e rendere le app Reactive
Come funziona il servizio Http di Angular 2 e il pattern Observable preferibile alle Promise per gestire le richieste asincrone e rendere le app Reactive
Link copiato negli appunti

Il servizio ArticoliService che abbiamo creato nella puntata precedente ci è servito come spunto per introdurre il concetto generale di servizio in Angular 2. Tuttavia esso non è molto realistico: nella pratica ci aspettiamo che l'elenco degli articoli sia persistito e recuperato tramite una Web API, quindi tramite una chiamata HTTP asincrona verso il server.

A differenza di quanto avveniva in Angular 1.x, in cui il servizio $http era un servizio integrato nel framework, in Angular 2 il corrispondente servizio Http non fa parte del framework ma va importato da un modulo specifico: il modulo HttpModule. Questo approccio consente da un lato di utilizzare in una applicazione solo quello che realmente serve e dall'altro di avere una maggiore libertà nella scelta del modulo, anche di terze parti, da utilizzare per effettuare chiamate HTTP.

Vediamo come trasformare in nostro servizio ArticoliService per fare in modo che sfrutti il servizio Http di Angular e recuperare dal server gli articoli della nostra applicazione.

Per prima cosa importiamo HttpModule all'interno del file di definizione del modulo della nostra applicazione, app.module.ts, come mostrato dal seguente codice:

import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
... 
@NgModule({
  imports: [
    HttpModule,
    ...
  ],
  declarations: [
    AppComponent,
    ...
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

In questo modo rendiamo le funzionalità implementate dal modulo disponibili a tutta l'applicazione.

Interveniamo quindi sulla definizione del servizio ArticoliService importando il servizio Http e modificando il costruttore come mostrato di seguito:

import { Http } from '@angular/http';
... 
@Injectable()
export class ArticoliService {
  private elencoArticoli: Articolo[]; 
  constructor(private http: Http) {} 
  ...
}

Nel costruttore abbiamo iniettato il servizio Http assegnandolo automaticamente ad una proprietà privata http. Questo fa sì che il servizio instanziato dal sistema di dependency injection di Angular sia disponibile a tutti i metodi che implementeremo.

A questo punto non ci resta che riscrivere il metodo getArticoli() del servizio ArticoliService in modo da sfruttare le funzionalità del servizio Http per recuperare i dati sugli articoli dal server. Ma prima di addentrarci nei dettagli dell'implementazione è opportuno fare una breve introduzione.

Come funziona il servizio Http di Angular 2

A differenza del servizio $http di Angular 1.x che era basato sul meccanismo delle Promise per gestire l'attività asincrona del protocollo HTTP, il servizio Http utilizza un approccio diverso, basato sugli Observable.

Gli Observable sono un elemento fondamentale di Reactive Programming, un modello di programmazione per la gestione di flussi di dati asincroni.

Gli Observable, infatti, rappresentano questo flusso di dati (stream) che, nel caso delle chiamate HTTP, provengono dal server in maniera asincrona. Questo modello di programmazione non è tipico di uno specifico linguaggio, ma può essere applicato in qualsiasi ambiente di sviluppo.

Nel caso di JavaScript, il suo supporto è reso disponibile da alcune librerie tra le quali segnaliamo RxJS, una libreria di Microsoft utilizzata da Angular 2.

Senza scendere nei dettagli di Reactive Programming, i metodi del servizio Http restituiscono un oggetto di tipo Observable. Possiamo vedere questo oggetto come un array di valori su cui possiamo applicare alcune elaborazioni e sul quale possiamo registrarci per essere avvisati della effettiva ricezione dei dati.

Per andare più sul concreto, vediamo quindi come possiamo riscrivere il metodo getArticoli() del nostro servizio ArticoliService. Per prima cosa importiamo la classe Observable dalla libreria RxJS con la seguente istruzione:

import { Observable } from 'rxjs';

Quidi importiamo l'operatore che intendiamo utilizzare, nel nostro caso l'operatore map(), come indicato di seguito:

import 'rxjs/add/operator/map';

A questo punto riscriviamo il metodo come mostrato di seguito:

getArticoli(): Observable<Articolo[]> {
	return this.http
		.get("/api/articoli")
		.map((responseData) => responseData.json());
}

Questa versione del metodo getArticoli() restituisce un oggetto Observable che contiene un array di articoli. Il valore restituito dal metodo è il risultato dell'invocazione del metodo get() di Http al quale è stato applicato l'operatore map().

L'operatore map() non fa altro che prendere ciascuna risposta inviata dal server e applicare la funzione passata come argomento creando un nuovo Observable con il risultato di questa mappatura. Nel nostro caso abbiamo specificato una arrow function che restituisce la conversione in JSON della risposta.

Sono disponibili numerosi operatori che consentono di elaborare i valori restituiti da una fonte asincrona di dati. Tra gli altri segnaliamo:

Operatore Descrizione Esempio
filter Filtra i valori restituiti da un Observable in base ad una condizione booleana specificata filter(x => x % 2 == 0)
interval Restituisce un Observable che genera un valore numerico ad ogni intervallo di tempo prefissato espresso in millisecondi interval(1000)
last Restituisce un Observable che contiene l'ultimo valore generato dall'Observable originario last()
max Restituisce il valore massimo generato da un Observable max()
merge Combina più Observable creandone uno che restituisce i valori asincroni generati da ciascuno di essi mergedObservable = observ1.merge(observ2, observ3);
concat Restituisce un Observable che concatena i valori restituiti da due o più Observable concatObservable = observ1.concat(observ2, observ3);
retry Restituisce un Observable identico all'originale sul quale, in caso di errore, si autoregistra per rieseguire l'operazione asincrona fino ad un massimo stabilito dal parametro passato retry(3)

Ritornando al nostro metodo, lo abbiamo quindi riscritto in modo che esso restituisca un Observable i cui valori sono array di articoli. Di conseguenza va anche rivisto l'utilizzo che facevamo del metodo getArticoli() all'interno del componente AppComponent. Le modifiche da apportare al componente sono evidenziati nel seguente codice:

import { Component} from '@angular/core';
import { Articolo } from './articolo';
import {ArticoliService} from './articoli.service' 
@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'] ,
  providers: [ArticoliService]
})
export class AppComponent {
  title = 'Articoli Tecnici';
  elencoArticoli; 
  // parte modificata
  constructor(private articoliService: ArticoliService) {
    articoliService.getArticoli()
                    .subscribe(result => {
                      this.elencoArticoli = result;
                    });
  }
  // fine modifica
}

Essenzialmente abbiamo modificato la modalità di chiamata del metodo getArticoli(). Dal momento che questo metodo restituisce un Observable, abbiamo registrato tramite il metodo subscribe() un'azione da eseguire quando arriva lo stream di dati dal server. In questo caso l'azione che verrà eseguita sarà l'assegnamento dei valori associati all'Observable, cioè un array di articoli, alla proprietà elencoArticoli.

Il resto del codice rimarrà come prima ed l'elenco degli articoli verrà renderizzato sulla pagina non appena arriverà la risposta del server alla richiesta HTTP.

Observable vs Promise

Una domanda che molto probabilmente qualche lettore si pone è: perché usare gli Observable al posto delle Promise? Quali sono le differenze e che vantaggi ci offrono i primi rispetto alle seconde?

La risposta semplice è che utilizzando gli Observable possiamo fare tutto quello che è possibile fare con le Promise. Non è vero il contrario, a meno di non complicare notevolmente il codice. In sintesi:

  • una Promise è legata ad un solo evento asincrono e l'unica cosa che è possibile fare è gestire il suo successo o il fallimento;
  • un Observable, invece, è associato ad una sequenza di eventi che possono essere combinati ed elaborati tramite gli operatori di cui abbiamo già accennato. Grazie ad essi possiamo effettuare operazioni, come ad esempio la riesecuzione di un'operazione asincrona fallita, difficilmente implementabili con le Promise.

Un altro ambito in cui non possiamo utilizzare le Promise per gestire eventi asincroni è nella gestione di flussi di dati continui, come ad esempio nella gestione di WebSocket. Mentre con gli Observable possiamo gestire i dati inviati dal server man mano che essi arrivano, con le Promise non abbiamo alcun modo per gestire questo flusso di dati, dal momento che tecnicamente la Promise non è mai soddisfatta fino a quando il flusso di dati non termina.

Inoltre, mentre non è possibile annullare una Promise, possiamo farlo con un Observable deregistrandolo tramite il metodo unsubscribe(), come mostra il seguente esempio di codice:

var registrazione = articoliService.getArticoli()
              		.subscribe(result => {
                 		this.elencoArticoli = result;
              		});
...
registrazione.unsubscribe();

In ogni caso, se nonostante quanto esposto, ci fossero ancora dubbi sull'utilizzo degli Observable o si ritenesse di non dover cambiare al momento modello di programmazione, è possibile convertire un Observable in una Promise utilizzando l'operatore toPromise(). In questo modo sarà possibile ad esempio scrivere il metodo getArticoli() facendo restituire una Promise come mostra il seguente codice:

getArticoli(): Promise<Articolo[]> {
    return this.http
                .get("/api/articoli")
		    .toPromise()
		    .then(response => response.json());
  }

Questo metodo sarà quindi utilizzato nel componente AppComponent come di seguito:

constructor(private articoliService: ArticoliService) {
    articoliService.getArticoli()
			  .then(result =>{
                      this.elencoArticoli = result;
                    });
  }

Link utili

Ti consigliamo anche