Aggiornato il 13/07/2018: Ampia revisione del codice per utilizzare convenzioni più recenti. Aggiornato Angular e RxJS alle ultime versioni disponibili, spostato esempi su StackBlitz.
L’obiettivo di questo post è dimostrare come funzionano le route resolves di Angular in 10 minuti, insieme a fornire codice di esempio su Stackblitz. Se hai già familiarità con le route resolves di AngularJS, ti consiglio di saltare l’introduzione e saltare direttamente alla sezione dell’app di esempio.
Tabella dei contenuti
Intro
App di esempio
- Step 1: Creare una classe Resolver
- Step 2: Aggiungere un Resolve Guard alla rotta
- Step 3: Ottenere i dati risolti dalla rotta attivata del componente
Note & Suggerimenti
- Evitare le chiamate API ridondanti
- Multiple risoluzioni sulla sulla stessa rotta
- Passare i parametri della rotta al risolutore
Note conclusive
Intro
Le risoluzioni delle rotte non sono altro che un modo per prerecuperare i dati di cui un componente ha bisogno prima di essere inizializzato. Di solito questi dati provengono da un’API. Diciamo che avete un componente il cui unico ruolo è quello di visualizzare un grafico delle vendite giornaliere per il mese; non ha senso rendere la vista o caricare questo componente prima che i dati delle vendite siano disponibili. Infatti, molte librerie di grafici daranno un errore se cercate di inizializzare un grafico prima di fornirgli i dati (e così farà *ngFor
). Naturalmente, potete facilmente aggirare questo problema nascondendo l’html con un *ngIf
o fornendo temporaneamente un array vuoto fino a quando i dati necessari sono caricati. Tuttavia, anche se si può fare a meno delle risoluzioni, implementarle aiuta a rendere il codice più leggibile e manutenibile:
- Eliminando il disordine confuso nel template e nel codice del vostro componente.
- Chiarisci il tuo intento mostrando quali dati devono essere pre-caricati.
Nonostante questi benefici, molti siti evitano di usare le risoluzioni in favore della visualizzazione della maggior parte del componente e mostrano uno spinner nelle sezioni in cui i dati sono ancora in fase di caricamento. In alcuni casi, questo approccio è desiderabile da un punto di vista UX, e una risoluzione non dovrebbe essere usata. Usa la tua discrezione.
Sample App
Open Sample App
Come puoi vedere, il nostro punto di partenza è un’app molto semplice con due percorsi – “Home” e “News”. Si naviga tra i percorsi cliccando sulla scheda corrispondente. Stiamo per aggiungere un resolve per pre-fetchare le notizie da un’API prima che il componente News venga caricato. Questo richiederà tre passi:
Passo 1: Creare una classe resolver che faccia una chiamata Http per pre-fetchare i dati di cui abbiamo bisogno.
Crea un nuovo file Typescript all’interno della cartella app
e nominalo:
news-resolver.service.ts
Poi copia e incolla il seguente codice nel tuo nuovo file (spiegato di seguito):
Ripercorriamo quello che abbiamo fatto nel codice precedente:
-
Aggiunte dichiarazioni di importazione ES6 per portare i moduli necessari.
-
Creato una nuova classe TypeScript
NewsResolver
. -
Aggiunto l’interfaccia
Resolve
alla nostra classe – questo è FACOLTATIVO, ma ogni classe che abbiamo intenzione di usare come resolver deve implementare un metodo resolve, quindi è una buona convenzione. -
Aggiunto un metodo
resolve()
aNewsResolver
– questo è il metodo responsabile della restituzione dei dati di cui abbiamo bisogno. Chiamare il metodo che restituisce i dati per una guardia “resolve” NON è opzionale – il resolver non funzionerebbe se il metodo fosse chiamato in altro modo. -
Se hai notato che nel codice sopra è stata erroneamente usata una richiesta
POST
invece di unaGET
, hai assolutamente ragione; in una vera applicazione, questa sarebbe una richiestaGET
. Gli esempi qui sfruttano, che è un sito che fornisce endpoint API di prova.
Prima di andare avanti, dobbiamo includere la classe resolver che abbiamo appena creato nel nostro modulo di routing. Navigate fino a app-routing.module.ts
e aggiungete NewsResolver
all’array providers
. Oppure, se hai appena iniziato a lavorare con Angular 2, sostituisci semplicemente il contenuto di app-routing.module.ts
con il codice qui sotto – le modifiche sono contrassegnate da note:
Con questo fatto, abbiamo ora definito la classe resolver. Nei passi seguenti, la aggiungeremo alla nostra rotta.
Codice dopo aver aggiunto la classe resolver
Passo 2: Aggiungere un Resolve Guard alla rotta.
In app-routing.module.ts
, cambiare la seguente linea di codice { path: 'news', component: NewsComponent }
in:
{ path: 'news', component: NewsComponent, resolve: { news: NewsResolver }}
Tutto quello che abbiamo fatto qui è stato aggiungere il resolve guard appena definito alla nostra rotta news
. Questo dice ad Angular che dobbiamo aspettare che il metodo resolve()
di NewsResolver
restituisca i dati prima di visualizzare il NewsComponent
.
È importante sottolineare che news
nella linea news: NewsResolver
di codice è quello che ho scelto per chiamare qualsiasi dato restituito dal resolver. Potete chiamarlo come volete.
Come nota un po’ tangenziale – se non avete familiarità con le guardie di percorso Angular in generale, e volete saperne di più, la documentazione è qui. Entrare nei dettagli delle guardie è fuori dallo scopo di questo post, ma dovete sapere che ci sono altre guardie oltre a resolve
disponibili, che è il motivo per cui ho tirato fuori il termine.
Codice con la Resolve Guard aggiunta
Step 3: ottenere i dati risolti dalla rotta attivata del componente.
Ora che abbiamo una classe resolver e l’abbiamo aggiunta alla nostra rotta, i dati vengono pre-fetched. Il passo finale è quello di accedere ai dati pre-fetched nel nostro NewsComponent
situato in app/news.component.ts
. Navigate in quel file e aggiungete il seguente modulo ES6:
import { ActivatedRoute } from '@angular/router';
poi fornite ActivatedRoute
nel costruttore del componente News aggiungendo il seguente codice in cima alla definizione della classe NewsComponent
:
constructor(private route: ActivatedRoute) {}
Come sapete, o potreste aver indovinato, ActivatedRoute
ci permette di accedere alle informazioni sulla rotta attualmente attiva, come l’url della rotta, i parametri della query, ecc. Quello che ci interessa qui sono i dati delle notizie che vengono caricati nel ActivatedRoute
dal resolve guard. Per ottenere i dati delle notizie risolte dalla rotta, aggiungete una proprietà per contenere i dati delle notizie:
public news: any;
e poi ottenete i dati dalla rotta attivata quando la NewsComponent
viene inizializzata:
ngOnInit(): void { this.news = this.route.snapshot.data; }
Questo è praticamente tutto. Cambiate il template del componente news per visualizzare le notizie dalla route resolve rimuovendo i contenuti attuali:
<div>This is just a placeholder for now. News will go here. </div>
e sostituendoli con:
Ora dovreste essere accolti dalle ultime notizie, “Il cielo è blu”, quando cliccate sulla scheda News.
Codice finito
Note & Consigli
– Le risoluzioni possono causare chiamate API ridondanti: Una risoluzione ottiene dati ogni volta che un componente viene caricato. Questo spesso si traduce in chiamate API non necessarie che influiscono negativamente sulle prestazioni. Se la vostra risoluzione ottiene dati che non cambiano frequentemente, considerate di scrivere i dati restituiti dalla risoluzione in una proprietà della classe resolver e semplicemente restituite tale proprietà se è già stata impostata. Per esempio, nel nostro esempio, aggiungeremmo una proprietà news
inizialmente non definita in questo modo: public news: any = undefined;
nel nostro risolutore di notizie. Poi, nel metodo resolve()
, controlliamo se la proprietà news
è già impostata e restituiamo il suo valore senza fare una chiamata API se lo è, cioè:
resolve(): Observable<any> { if (this.news) { return this.getSavedNews(); } else { return this.getNewsFromApi() } }
Esempio completo di codice qui sotto. Se la sintassi degli osservabili vi sembra un po’ insolita, è perché sto usando RxJS 6
. C’è un buon tutorial sui cambiamenti recentemente introdotti in RxJS
qui se avete bisogno di un ripasso.
Ritornare i dati salvati, se già recuperati dall’API
Naturalmente, si potrebbe andare oltre e impostare un periodo di tempo durante il quale i dati sono validi non solo salvando i dati, ma aggiungendo un’altra proprietà timestamp e facendo una chiamata API se i dati sono più vecchi di x.
– Proprio come in AngularJS, è possibile utilizzare più resolve sulla stessa rotta. Le chiamate resolve sono fatte in parallelo e il componente si caricherà solo dopo che tutte le chiamate avranno restituito i dati. Per usare risoluzioni multiple, basta aggiungerle alla rotta:
Allora i dati di risoluzione aggiuntivi possono essere accessibili dall’istantanea della rotta proprio come una singola risoluzione:
ngOnInit(): void { this.news = this.route.snapshot.data; this.alternativeNews = this.route.snapshot.data; }
Codice con risoluzioni multiple
– Il resolver ha accesso ai parametri della rotta. Diciamo che avete un componente che mostra una lista di titoli di notizie. Quando si clicca su una storia, si apre un altro componente che mostra l’articolo completo. Prima di caricare quel componente, abbiamo bisogno di pre-caricare il contenuto della notizia il cui titolo è stato cliccato – questo può essere fatto nel metodo resolve. La classe resolver ha accesso al ActivatedRoute
, quindi possiamo ottenere l’id della storia che è stata cliccata:
resolve(route: ActivatedRouteSnapshot) { let id: any = route.params); return this.getNewsStory(id); }
Questo è abbastanza autoesplicativo. Per un esempio, guardate il nuovo file src/news-story-resolver.service.ts
nel link qui sotto. I collegamenti al nuovo componente sono stati aggiunti nella scheda News (news.component.ts
).
Codice con risoluzione parametrizzata