The playground

More information here

Smoother UX on Page Load with Angular Resolvers

Come sviluppatore, stai sempre cercando di ottimizzare il tuo codice. Ciò include la velocità con cui si presenta all’utente l’interfaccia utente completamente caricata, che spesso dipende dai dati provenienti da un database. Inevitabilmente, si inizia a cercare modi per risolvere i dati immediatamente dopo la navigazione in una nuova pagina / area della propria applicazione, […]

Come sviluppatore, stai sempre cercando di ottimizzare il tuo codice. Ciò include la velocità con cui si presenta all’utente l’interfaccia utente completamente caricata, che spesso dipende dai dati provenienti da un database.

Inevitabilmente, si inizia a cercare modi per risolvere i dati immediatamente dopo la navigazione in una nuova pagina / area della propria applicazione, senza che l’utente incontri ciò che è noto come page JANK.

Questo è quando la pagina si muove su e giù durante il caricamento di alcuni componenti. Può influenzare in modo significativo l’UX al punto in cui sembra “buggy”.

Angular fornisce un approccio intuitivo al pre-recupero dei dati prima che il percorso venga caricato; prima che il percorso navigato si risolva.

È chiamato un Risolutore angolare.

Un Risolutore angolare è essenzialmente un Servizio Angolare. Classe iniettabile fornita a un modulo di routing nella configurazione del percorso. Questo tipo speciale di servizio viene iniettato ed eseguito quando viene navigata la rotta contenente.

Il Resolver risolve quindi i dati prima del caricamento della pagina, che diventa disponibile tramite il servizioActivatedRoute. Ciò fornisce un modo semplice ed efficiente per garantire che l’utente disponga dei dati il più rapidamente possibile prima che un componente importante per il caricamento iniziale della pagina ne abbia bisogno.

Un altro modo per utilizzare un risolutore angolare è usarlo come metodo per popolare istantaneamente i metadati SEO.

Con un resolver, si fornisce una garanzia che i dati esisteranno prima che la pagina sia caricata, assicurando che tutto abbia ciò di cui ha bisogno all’inizializzazione.

Scomponiamo il risolutore angolare

Un risolutore angolare è una classe che implementa l’interfaccia Resolve. L’interfacciaResolve richiede l’implementazione di una funzione all’interno della classe denominataresolve.

Ecco la firma dell’interfaccia Resolve signature

export interface Resolve<T> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T {
return 'data';
}
}

Come possiamo vedere dalla firma dell’interfaccia, richiede un argomento genericoT che sarà il tipo dei nostri dati risolti.

La funzione resolve restituisce unObservablePromiseo solo i dati di tipoT. Pertanto, vi è un suggerimento che questo dovrebbe essere gestito in modo asincrono, specialmente se si recuperano i dati dal database.

Lo scopo principale della funzione resolve è che deve essere completata. Questo fatto deve essere ricordato quando si recuperano dati in osservabili. L’osservabile deve essere completato. Dopo tutto, è un risolutore.

Se l’osservabile non viene completato, i dati non verranno mai risolti e la pagina non verrà mai caricata. Pertanto, è necessario definire il punto in cui non è necessario prendere altri valori e i dati possono essere risolti, poiché è stato ottenuto tutto ciò che serve dal database. Quando si utilizzano flussi di dati asincroni come osservabili, questo è un caso d’uso per gli operatori pipeable da RXJS.

Gli operatori pipeable che vengono in mente quando si pensa di completare un flusso di dati in base a una condizione, è una combinazione difiltertakefirst. Con questa combinazione, è possibile filtrare tutti i valori che non si vuole prendere, ad esempio null o undefined o un array vuoto , quindi take il primo valore valido con take(1).

Gli operatori che potrebbero essere necessari per completare un osservabile in anticipo quando si riscontrano problemi o errori, in cui si desidera restituire null o reindirizzare, sonocatchError etimeout . Una combinazione di timeout e catchError è utile se i dati richiedono troppo tempo e si desidera restituire null in modo da poter riprovare all’interno del componente, o si desidera reindirizzare.

Se i dati non si risolvono rapidamente, si basa su filtri complessi, logica, enormi quantità di chiamate al database, è probabile che si verifichino problemi di volta in volta.

È preferibile determinare la quantità minima di chiamate al database e i dati minimi necessari per caricare correttamente e con garbo la pagina.

Di conseguenza, potresti trarre vantaggio dal trascorrere del tempo, prima di implementare il tuo resolver, per concentrarti sulla separazione del contenuto “above the fold” dai dati che possono essere caricati quando la pagina viene inizializzata.

Pertanto, è possibile dividere i dati necessari per una UX uniforme, dal resto dei dati che potrebbero essere chiamati dal componente, piuttosto che dal risolutore.

È quindi possibile gestire esclusivamente il contenuto minimo, sopra la piega, attraverso il resolver.

Questo approccio dinamico al caricamento della pagina può essere assistito con l’uso di scheletri. In modo che se l’utente scorre istantaneamente verso il basso, è possibile dare all’utente l’indicazione che il contenuto viene caricato, migliorando successivamente UX.

Passaggio 1: Creando il Resolver

Dobbiamo creare il Resolver angolare. Tuttavia, non esiste un comando CLI angolare che genera un risolutore. Pertanto, dovremo scrivere il decoratore (metadati resolver) da soli.

Fortunatamente, sono solo poche righe di codice che formano il boilerplate per un risolutore, e possiamo prendere il decoratore iniettabile da un servizio esistente se stai lottando per ricordarlo.

Annotare la classe Resolver profilo con un decoratore iniettabile

In primo luogo, forniamo il decoratore iniettabile conprovidedIn: any nella configurazione.

@Injectable({ providedIn: 'any'})

Nomineremo quindi il nostro resolver aggiungendo la convenzioneResolver. Per questo esempio, risolveremo i dati del profilo (dati utente), quindi lo chiameremo ProfileResolver .

Poiché è un resolver e Angular riconosce la funzione dei resolver, possiamo implementare la classeResolve, che fornirà la firma che dobbiamo implementare nella nostra funzione di risoluzione per risolvere correttamente i dati.

@Injectable({providedIn: 'any'})
export class ProfileResolver implements Resolve<Profile> {
}

La nostra funzione resolve restituirà un osservabile con dati conformi all’interfacciaProfile. Pertanto, forniremo l’interfacciaProfile come argomento generico per la classe resolver e la funzioneresolve(). In questo modo ci siamo conformati ai requisiti angolari per un resolver.

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T {
return;
}

L’implementazione resolve ci fornisce due parametri se sono necessari,routeestate. Questi sono popolati automaticamente e sono accessibili dall’interno della nostra funzioneresolve().

Successivamente, dobbiamo effettivamente risolvere i dati reali dal database, che sarà il nostro prossimo passo.

Recupero dei dati dal database

Per recuperare i dati per la nostra funzione resolve, dovremo iniettare il servizio che fornisce i dati; uno che interagisce con il database.

Prendiamo ciò di cui abbiamo bisogno per risolverlo rapidamente in modo che l’utente navighi prontamente e con successo. Ai fini di questa demo, non ci preoccuperemo del servizio sottostante che si occupa del database. Ci limiteremo a iniettare il servizio utilizzando dependency injection nell’argomento del costruttore per la nostra classe ProfileResolver.

Poiché i nostri dati si presentano sotto forma di un flusso di dati osservabile con più valori che emettono in modo asincrono, avremo solo bisogno ditake(1) utilizzando l’operatore pipeabletake importato darxjs/operator . Altrimenti l’osservabile non sarebbe mai completo e il risolutore non avrebbe mai resolve risolto.

Abbiamo solo bisogno di un’emissione/valore etake completa l’osservabile per noi.

È così semplice creare un resolver; abbiamo solo bisogno di restituire l’osservabile nella funzioneresolve() di cui angular gestirà la sottoscrizione.

La classe ProfileResolver

Gestiamo eventuali errori nel recupero dei dati reindirizzando a un percorso padre.

Bonus: Popola rapidamente i metadati SEO dinamici prima che il percorso abbia caricato

I vantaggi del popolamento dei tag <meta> hanno immediatamente evidenti vantaggi. Più velocemente i nostri metadati SEO vengono popolati, più velocemente il nostro SEO riflette correttamente e accuratamente il contenuto della pagina.

Questo significa che è più facile e veloce per i robot, come quelli gestiti da motori di ricerca come Google e Bing, per eseguire la scansione del sito e recuperare il contenuto.

Questo non è così importante sulle pagine pre-renderizzate o quelle renderizzate da Angular Universal, perché tutto il rendering è completo prima che i robot ricevano il contenuto.

Tuttavia, se ti affidi alla (spesso discutibile) capacità dei robot di Google di analizzare javascript per il tuo SEO, o hai una soluzione on-demand come puppeteer che ha bisogno della certezza che il SEO sarà corretto prima di restituire il DOM reso, allora un resolver che include SEO dovrebbe aiutare. Quindi aiuta quando il crawler è limitato nel tempo.

Separa anche le preoccupazioni dal componente, in modo che il componente non debba occuparsi di nulla di SEO correlato. Uno dei motivi principali per cui mi piacciono i risolutori.

Passo 2: Iniettare il resolver nel modulo di routing

Il ProfileRoutingModule in cui forniremo il nostro resolver è un modulo lazy loaded. Pertanto, il nostro percorso di root sarà vuoto con il token parametro userSlug, che avremo bisogno di recuperare i dati del profilo corretto.

Per fornire il nostro resolver, forniamo semplicemente un oggetto con il nome dei nostri dati come chiave e il resolver specifico come valore che sarà responsabile della risoluzione di tali dati.

Puoi nominare la chiave come preferisci, ma la chiameremo semplicemente data.

Il nostro ProfileRoutingModule

Questo è tutto ciò che è necessario nel modulo di routing per poter utilizzare il nostro resolver.

Successivamente, dobbiamo recuperare e utilizzare i nostri dati nel componente.

Punto 3: Inizializzare il componente con i dati risolti

Ora che abbiamo risolto i nostri dati sull’attivazione del percorso, i dati sono accessibili tramite il servizioActivatedRoute. Poiché abbiamo a che fare con gli osservabili, in tutta l’applicazione, creeremo un flusso che si lega alla proprietà data che sarà il nostro dato risolto.

Per prima cosa, inietteremo il ActivatedRoute nel costruttore del nostro ProfileComponent . Successivamente, assegneremo this.route.dataalprofile$ osservabile. Vorremmo anche passare all’utilizzo di un osservabile quando i dati aggiornati arrivano dal database in modo da avere nuovi dati quando interagiamo con l’app.

Per questo, useremo startWith in modo da iniziare il nostro flusso con il valore che è facilmente accessibile da this.route.snapshot.data. Quindi accediamo alla proprietàdata comethis.route.snapshot.datastartWith indica un valore con cui iniziare, come prima emissione del nostro flusso.

Il nostro componente profilo

Ciò che i dati immediatamente accessibili fa per il componente

I dati immediatamente accessibili riduce il tempo trascorso a caricare le singole parti di quella pagina, che viene osservato dall’utente. Il risultato di non utilizzare un resolver come questo è che la pagina potrebbe apparire come caricata in modo frammentato, il che non è visivamente piacevole.

Di conseguenza, dovrai prestare attenzione a quali elementi dei tuoi modelli HTML dipendono da quali dati. Dovresti quindi scrivere il tuo resolver per supportare questi elementi e l’effetto complessivo sul caricamento della pagina UX.

Esistono diversi modi in cui il componente può caricare frammentato

  • Uno di questi è se si dispone di unngIf in più parti del modello HTML.
  • Un altro èngFor .

È consigliabile limitare la quantità di singolingIf scritti allo scopo di limitare la quantità di ridimensionamento che il browser deve fare.

Caricare la pagina prima di ottenere i dati può causare parti della pagina a saltare, lag e ridimensionare costantemente, causando la UX a soffrire.

L’implementazione di un resolver potrebbe essere la differenza tra l’utente che sperimenta 3-5 secondi di salto e ridimensionamento rispetto a 0,5 secondi, che è spesso troppo veloce per essere dannoso per l’UX generale.

Questo è tutto! Abbiamo un resolver con una UX migliorata al caricamento della pagina.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.