Angular Best Practices

Angular Best Practices - Federico Rinaldi
Angular Best Practices - Federico Rinaldi

Dopo tanti anni di utilizzo e di esperienza di sviluppo su Angular, ho cercato di collezionare una serie di best practices che ho trovato utili ai fini dell’organizzazione e della scrittura di un buon codice, vista anche la penosa produzione di approfondimenti in lingua Italiana.

Naturalmente non riuscirò ad essere esaustivo su ogni aspetto di un mondo vasto e complesso come ormai è diventato lo sviluppo front-end, ma questo articolo vuole essere un remainder per me e un piccolo aiuto (spero) a chi si sta approcciando questo universo.

Ho cercato quindi di suddividere la tematica in una serie di punti che riassumono i principali focus da tenere sotto osservazione.

💡
Utilizzo di Angular/cli

L’Angular/cli (Angular Command Line) è il coltellino svizzero per lo sviluppo di un applicazione Angular. Ci permette di creare, sviluppare e testare le nostre applicazioni.

💡
Utilizzo intensivo di ES6

Utilizzare il più possibile la sintassi e le funzionalità di ECMAScript 6, questo ci permetterà di scrivere codice più bello, funzionale e moderno.

💡
Lazy Loading

Prima di tutto organizzare il proprio progetto in moduli, principali e non, magari riutilizzabili, poi caricarli tramite il Lazy Loading; Questo ci permetterà prima di tutto di spezzettare l’applicazione in più blocchi e nello stesso tempo di velocizzarne il caricamento.

In sostanza caricare un modulo in Lazy significa che il vostro browser scaricherà quella “porzione” di applicazione solamente quando richiesta e non massivamente all’avvio.

💡
Tipizzare, Tipizzare, Tipizzare

Abbiamo a disposizione Typescript, usiamolo!

Creiamo interfacce, classi, oggetti, tipizziamo il più possibile, evitiamo di abusare del type “any”, rendiamo il codice più robusto, leggibile a prova di confusione.

Utilizziamo Safe Data Types, per restringere ancor di più le possibilità di errore.


type Nomi = "Federico" | "Antonio";
 
const buddy1: Nomi = "Federico";
const buddy2: Nomi = "Antonio";
const buddy3: Nomi = "Marco";

// L'ultima riga restituirà il seguente errore: Type ‘"Marco"’ is not assignable to type 'Nomi'.

💡
TrackBy

Utilizzare la direttiva *ngFor senza la funzione trackBy farà calare drasticamente le performance della nostra applicazione, questo perché ogni qualvolta che, ad esempio, in un iterazione di una lista aggiungiamo/rimuoviamo/modifichiamo un elemento, tutta la lista viene renderizzata di nuovo.

Con la funzione trackBy, Angular, utilizzando il suo valore di ritorno, genera una diff tra le 2 liste, prima e dopo la modifica, ed integra solo gli elementi realmente modificati.

Chiaramente se la nostra lista si compone di pochi elementi l’impatto sarà trascurabile, altrimenti come potete ben immaginare renderizzare ogni volta liste con migliaia di elementi può, anzi sicuramente, determinerà un peggioramento delle performance della vostra applicazione.


// In component.ts

trackByProductCode(index:number,product:Product){
	
    return product.code;

}

// In component.html

*ngFor='let prd of products; trackBy:trackByProductCode'>

💡
Memory Leaks in Observable

Fare molta attenzione, durante la navigazione tra components, alla stragrande quantità di observable che sicuramente avete creato, questo perché nel passaggio, ad esempio, tra component1 e component2, il primo viene distrutto e il secondo inizializzato, se nel primo avete sottoscritto degli observable questo può causarvi un memory leak.

Come ovviare al problema? Vediamo qualche soluzione :


takeUntil()

TakeUntil fa un controllo sul secondo Observable e una volta che il valore viene restituito, eliminerà la sottoscrizione e chiuderà il giro.


this.productService.getProductsList()
.pipe(takeUntil(this.prdUnsubscribe))
.subscribe(response => {this.products = response;})

....

ngOnDestroy(){
this.prdUnsubscribe.next();
this.prdUnsubscribe.complete();
}




Async pipe

Utilizzate la pipe async che sottoscriverà automaticamente ad un Observable e ritornerà il valore emesso più aggiornato.


*ngFor="let prd of productService.getProductsList() | async";
{{prd.productName}}



take()

Utilizzate il take in ogni vostra api call, così sarete sicuri che i dati vengano recuperati una sola volta.


this.productService.getProductList()
      .pipe(take(1))
      .subscribe(response = {this.products = response;})


💡
Evitare la logica nel template

Anche se formalmente corretto, controllate che tutto ciò che è logica di business della vostra applicazione possa essere portato all’interno del componente, questo vi alleggerirà durante gli unit test e nel caso di futuri cambiamenti nel template (cosa sicura :))

💡
Cache

Utilizzate la cache nelle vostre api call, soprattutto nelle chiamate che recuperano dati stabili e con pochissima frequenza di aggiornamento, in modo tale da evitare caricamenti ridondanti.

Noterete sicuramente un miglioramento della velocità dell’applicazione.

💡
Virtual Scroll

A chi non è capitato di gestire lunghe liste piene di dati? Utilizzate il CDK Virtual Scroll per migliorare l’efficienza della navigazione.


Ci sarebbe molto altro da dire, ma come accennavo all’inizio dell’articolo, queste sono solo poche semplici regole, maturate nel corso dell’esperienza, che spero possano essere di aiuto nella realizzazione di prodotti più leggeri, veloci e puliti a chi si sta approcciando a questo mondo da poco tempo.

Grazie a tutti, per commenti/confronti/idee :

info [at] federicorinaldi [dot] com
Oppure seguitemi su Mastodon : https://mastodon.uno/@FedeRinaldi83

Federico Rinaldi

Federico Rinaldi

Senior Fullstack Developer in Rome, Italy. Appassionato di Musica e Fotografia, Bass Player
Rome