10-05-2021 - Frontend

Come ottenere il massimo dalla configurazione di Angular

Best practices per lo sviluppo in Angular. Ecco il primo di una serie di articoli del nostro Mirko Rapisarda.

 

La prima volta che mi sono affacciato allo sviluppo FE in Angular mi ha un po’ spiazzato, soprattutto per chi come me proviene da un ambito back-end (nello specifico PHP) ed è abituato a utilizzare una tipizzazione forte all’interno dei propri progetti (anche grazie a tools di analisi statica come Psalm o PHPStan).

Lavorando alla costruzione di progetti in Angular, infatti, non ho potuto fare a meno di notare che, nonostante il framework utilizzi ampiamente TypeScript, si tende spesso a lasciarlo in modalità “silenzioso”, non sfruttando appieno le potenzialità del compilatore.

Anche nell’organizzazione delle cartelle ho trovato spesso parecchia confusione, con la tendenza di inserire parecchia logica nei componenti, quando in realtà quest’ultimi dovrebbero semplicemente occuparsi di prendere i dati e mostrarli all’utente relegando il fetching dei dati e le operazioni di manipolazione degli stessi, che non riguardano strettamente lo stato dei componenti, ai servizi.

Questi scenari a cui ho assistito mi hanno portato alla decisione di creare una serie di articoli con l’obiettivo di far luce su alcuni concetti di Angular e sulle best practices da adottare per migliorare lo sviluppo su questo framework

 

In questo primo articolo andrò a indicare alcuni consigli su come ottenere il massimo già dall’installazione di Angular e dalla configurazione di TypeScript.

 

Angular Strict Mode

La prima operazione che possiamo effettuare è l’abilitazione della modalità strict di Angular già in fase di installazione (nel caso di versioni minori della 12), attraverso il comando:

ng new [project-name] --strict

Questa modalità abilita nello specifico:

  • La modalità strict di TypeScript e altri flag che adesso andremo ad analizzare;
  • Abilita i flag di Angular: strictTemplates e strictInjectionParameters;
  • Riduce le dimensioni dei budgets del ~75%.

 

TypeScript Strict Mode

La strict mode di Angular abilita automaticamente i seguenti flag di TypeScript all’interno del file tsconfig.json. Questi potrebbero essere abilitati anche editando manualmente il file di configurazione di TypeScript:

 

strictPropertyInitialization

Questo flag segnala un errore di compilazione se le proprietà di una classe vengono dichiarate, ma non inizializzate nel costruttore. Pertanto, il seguente frammento di codice genererebbe un errore:

1@Component({...})
2class AppComponent {
3  // Error: Property 'title' has no initializer
4  @Input() title: string;
5}

 

Per fixarlo possiamo inizializzare la proprietà nel costruttore o direttamente in fase di dichiarazione:

1@Component({...})
2class AppComponent {
3  @Input() title = '';
4}

 

strictNullChecks

Questo flag segnala un errore qualora tentassimo di utilizzare null o undefined quando in realtà ci si aspetta di ricevere un valore concreto:

1interface Person {
2    firstName: string;
3    lastName: string;
4    age: number;
5}
6
7// Error: Type 'null' is not assignable to type 'Person'
8const developer: Person = null;
9
10console.log(`${developer.firstName} ${developer.lastName}`);

 

noImplicitAny

Questo flag è il mio preferito, perché consente la segnalazione di un errore qualora lasciassimo il tipo any come tipo implicito inferito. Ciò non vuol dire che non possiamo utilizzare più il tipo any (anche se personalmente sconsiglio il suo utilizzo frequente, perché rende vano l’utilizzo di TypeScript), ma che semplicemente dobbiamo specificare in maniera esplicita il tipo (anche se questo è any) per ogni proprietà, parametro e variabile dichiarata.

1@Component({...})
2export class AppComponent {
3  // Error: Parameter 'value' implicitly has an 'any' type
4  onAddClick(value) {
5    // Do stuff...
6  }
7}

 

Per correggere l’errore possiamo indicare esplicitamente il tipo any oppure indicare un tipo più specifico:

1@Component({...})
2export class AppComponent {
3  onAddClick(value: Person) {
4    // Do stuff...
5  }
6}

 

strictBindCallApply

Questo flag è un po’ più anonimo, in sintesi consente a TypeScript di verificare la correttezza dei tipi passati come parametro anche qualora una funzione venisse chiamata tramite i metodi call, bind e apply.

1function toInt(x: string) {
2  return parseInt(x);
3}
4
5const number1 = toInt.call(undefined, "10");
6
7// Error: Argument of type 'boolean' is not assignable to
8// parameter of type 'string'
9const number2 = toInt.call(undefined, false);

 

strictFunctionTypes

Questo flag verifica che quando si assegnano le funzioni i parametri e i valori restituiti siano compatibili con i sottotipi:

1function log(x: string) {
2  console.log(x.toLowerCase());
3}
4
5type StringOrNumberFunc = (ns: string | number) => void;
6
7// Error: Type '(x: string) => void' is not assignable to type // 'StringOrNumberFunc'
8const func: StringOrNumberFunc = log;

 

Ci sono altre tre opzioni che, seppur non vengono abilitate automaticamente impostando la modalità strict di Angular, consiglio altamente di impostarle manualmente:

 

noImplicitReturns

Questo flag consente a TypeScript di segnalare un errore qualora tutti i percorsi di una funzione non restituiscono un valore:

1// Error: Not all code paths return a value
2function lookupHeadphonesManufacturer(color: string) {
3  if (color === 'blue') {
4    return 'beats';
5  }
6
7  'bose';
8}

 

noUnusedLocals

Questo flag consente a TypeScript di segnalare un errore qualora una variabile dichiarata non viene utilizzata:

1// Error: 'OnInit' is declared but its value is never read
2import { Component, OnInit } from '@angular/core';
3
4@Component({
5  selector: 'app-root',
6  templateUrl: './app.component.html',
7  styleUrls: ['./app.component.scss']
8})
9export class AppComponent {
10  title: string = 'Devmy Example Project';
11  // Error: 'subtitle' is declared but its value is never read
12  private subtitle: string = 'Hello World';
13
14  // Error: 'log' is declared but its value is never read
15  private log(value: string): void {
16    console.log(value);
17  }
18}

 

noUnusedParameters

Questo flag consente a TypeScript di segnalare un errore qualora un parametro di funzione non viene utilizzato:

1import { Component } from '@angular/core';
2
3@Component({
4  selector: 'app-root',
5  templateUrl: './app.component.html',
6  styleUrls: ['./app.component.scss']
7})
8export class AppComponent {
9  title: string = 'Devmy Example Project';
10  // Error: 'value' is declared but its value is never read
11  onOptionChange(value: number) {
12    
13  }
14}

 

Qualora sia obbligatorio indicare il parametro, ma tuttavia non ci occorre utilizzarlo, possiamo semplicemente dire a TypeScript di ignorarlo sostituendo o anteponendo un underscore al nome del parametro:

1import { Component } from '@angular/core';
2
3@Component({
4  selector: 'app-root',
5  templateUrl: './app.component.html',
6  styleUrls: ['./app.component.scss']
7})
8export class AppComponent {
9  title: string = 'Devmy Example Project';
10
11  onOptionChange(_value: number) {
12    
13  }
14}

 

 

Strict Angular Template

Abilitando la modalità strict di Angular vengono automaticamente attivati anche i flag:

 

strictTemplates

Se abilitato, Angular verificherà all’interno dei file template che venga rispettata l’interfaccia dei tipi:

app.component.ts

1interface User {
2  firstName: string;
3  lastName: string;
4}
5
6@Component({
7  selector: 'app-root',
8  templateUrl: './app.component.html',
9  styleUrls: ['./app.component.scss']
10})
11export class AppComponent {
12  title: string = 'Devmy Example Project';
13  users: User[] = [
14    {
15      firstName: 'Mario',
16      lastName: 'Rossi'
17    },
18    {
19      firstName: 'Maria',
20      lastName: 'Rossi'
21    },
22    {
23      firstName: 'Carlo',
24      lastName: 'Rossi'
25    }
26  ];
27}

 

app.component.html

1<div>
2  <ul>
3    <li *ngFor="let user of users">
4      <!-- Property 'age' does not exist on type 'User' -->
5      {{ user.firstName }}-{{ user.lastName }}: {{ user.age }}
6    </li>
7  </ul>
8</div>

 

strictInjectionParameters

Se abilitato, Angular segnalerà un errore qualora non sia possibile determinare quale tipo iniettare per il parametro specificato nel costruttore.

 

 

Angular Bundle Budgets

Quando si esegue la build, l’applicazione Angular deve rispettare le dimensioni massime di bundle consentite. Se la nostra applicazione supera queste dimensioni, la build fallirà. Questo ci obbliga a deployare delle applicazioni che siano performanti e che non abbiano delle dipendenze eccessivamente “pesanti”.

Di default Angular possiede dei budgets abbastanza alti:

  • Fino a una dimensione di build di 2 MB riceveremo un semplice warning, superati i 5 MB si otterrà un errore durante la fase di compilazione;
  • Fino a una dimensione di stile dei componenti di 6 KB riceveremo un semplice warning, superati i 10 KB si otterrà un errore durante la fase di compilazione.

Attivando la modalità strict di Angular, questi budgets vengono ridotti del ~75%, permettendoci di identificare immediatamente qualora abbiamo introdotto una dipendenza eccessivamente “pesante”:

  • Fino a una dimensione di build di 500 KB riceveremo un semplice warning, superato 1 MB si otterrà un errore durante la fase di compilazione;
  • Fino a una dimensione di stile dei componenti di 2 KB riceveremo un semplice warning, superati i 4 KB si otterrà un errore durante la fase di compilazione.

Per assicurarci di rispettare le dimensioni di budgets impostate dobbiamo:

  • Utilizzare il tool source-map-explorer per ispezionare l’impatto che hanno le dipendenze installate all’interno dell’applicazione;
  • Utilizzare il lazy-loading;
  • Evitare importazioni di grandi dimensioni negli stili dei componenti.

 

 

Conclusioni

Traendo le conclusioni, seppur la modalità strict di Angular può sembrare in un primo momento eccessivamente restrittiva, vi assicuro che attivandola il vostro codice sarà più robusto, meno soggetto a bug e più semplice da aggiornare. Dalla versione 12 il team Angular ha pensato bene di rendere di default la creazione dei progetti in questa modalità ma per i progetti pre-esistenti potrebbe essere una manna dal cielo andare ad attivare i vari flags e capire cosa va e cosa non va!

 

Ecco spiegato perché sia utile abilitare la modalità strict di Angular. Quali altri consigli ci aspettano in futuro? Forse tools per velocizzare lo sviluppo e l’utilizzo di Angular?

Lo scopriremo presto!

Autore

Mirko Rapisarda

Developer @Devmy, PUG Catania Organizer

Da sempre considera programmare più come una passione che come un lavoro. D'altra parte, questa è la ragione principale che spinge i programmatori a imparare nuovi linguaggi, nuove tecnologie e metodologie ogni giorno. Senza questa "fame" di sapere non è possibile raggiungere grandi conquiste in questa professione.

La sua passione lo ha portato allo sviluppo di applicazioni web usando tecnologie quali PHP, MySQL, Symfony, JavaScript, Node, Angular, tuttavia sperimenta volentieri anche con altri linguaggi e tecnologie come Vue, VB e Flutter. Grande sostenitore della metodologia Agile e dei principi presenti all'interno del manifesto che lo hanno fortemente ispirato nella creazione di una serie di libri che ben esprimono i vantaggi di seguire questi principi.

Precedentemente docente presso PED Academy.

.Devmy su linkedin