
A pochi mesi dalla pubblicazione della versione 9 (descritta qui), il team di Angular ci ha sorpreso col rilascio della versione 10, cercando di colmare i ritardi accumulati e promettendo il rilascio della versione 11 per la fine dell’autunno.

Nelle dipendenze per lo sviluppo, Typescript è balzato alla versione 3.9.5. A mio parere, si tende a dare poca importanza al dettaglio della versione di un linguaggio, ma è innegabile che uno sviluppatore medio che usi Angular ha fatto un investimento di apprendimento di Typescript ormai risalente a 3-4 anni fa, quando la versione di Typescript inserita nella ng cli era la 2.qualcosa. Il linguaggio, oggi, è profondamente diverso e più sofisticato e andrebbe costantemente aggiornata la propria preparazione. Il rischio che si corre è consultare la documentazione di Angular, trovare un pezzo di snippet di codice bizzarro e copiarlo nel proprio progetto senza avere un’idea di cosa stia succedendo.
Voglio mostrarvi qualche piccolo esempio (non collegati alla versione 3.9.5 di Typescript).
Partiamo dalle due seguenti interfacce:
export interface Student {
id: number;
age: number;
name: string;
surname: string;
}
export interface Worker {
companyId: number;
salary: number;
}
Possiamo definire un nuovo tipo che può essere uno Student o un Worker!
export type MyUnionType = Student | Worker;
Ma cosa succede se abbiamo la necessità di sapere se una variabile è uno Student o un Worker? Possiamo definire una funzione il cui tipo di ritorno sia un type predicate:
isStudent(person: MyUnionType): person is Student {
return (person as Student).name !== undefined;
}
e usarla successivamente nel codice:
if (this.isStudent(person)) {
console.log(person.name);
} else {
// ....
}
Ė interessante notare che nell’istruzione else abbiamo esplicitamente un worker.
Possiamo definire anche un tipo che sia la combinazione di due o più altri tipi.
export type MyIntersectionType = Student & Worker;
Sono altresì disponibili dei tipi generici più avanzati. Consideriamo ad esempio Partial<T>.
Supponiamo di creare una funzione che modifichi un Worker:
const updateWorker = (id: number, worker: Worker) => {};
updateWorker(1, { salary: 1000});
Questo codice non compila perché companyId manca nell’oggetto che abbiamo passato alla funzione. Posso correggere la funzione nella maniera seguente:
const updateWorker = (id: number, worker: Partial<Worker>) => {};
eliminando l’errore.
Abbiamo altri utility type come ad esempio Record<K,T>. Definiamo un type alias Headquarter e associamo a ciascuna sede un Worker.
export type Headquarter = 'Napoli' | 'Roma' | 'Milano';
const x: Record<Headquarter, Worker> = {
Napoli: { companyId: 3, salary: 3000 },
Roma: { companyId: 4, salary: 3000 },
Milano: { companyId: 5, salary: 3000 },
};
E se volessi creare un nuovo tipo a partire da Student ma scegliendo solo alcune delle sue proprietà?
export type StudentPreview = Pick<Student, 'id' | 'surname'>;
Se siete interessati ad approfondire il tema della Composition in Typescript vi rimando, oltre alla documentazione ufficiale, al seguente video.
Torniamo ora ad Angular 10, con una nuova opzione nel comando ng new che crea un nuovo progetto: ng new –strict.
Non solo andiamo a migliorare la manutenibilità del progetto riuscendo a trovare bug più velocemente possibile, ma soprattutto permettiamo alla CLI di eseguire delle ottimizzazioni avanzate sulla nostra app. Nel dettaglio:
- viene abilitato lo strict mode in Typescript (addio variabili any!)
- viene abilitato il type check nei template
- vengono attivate regole più restrittive di linting (sperando che non mandiate in review codice pieno di segnalazioni ignorate!)
- i valori di default del bundle budget sono stati ridotti del 75%
- La nostra app viene configurata come side-effect free e ciò migliora l’efficienza dell’algoritmo di tree shaking
Il bundle budget è una feature poco nota di Angular, che consente di porre una soglia sulla dimensione dei bundle dell’applicazione. In definitiva, vogliamo un warning o che la compilazione fallisca se abbiamo superato quel limite. I budget sono impostati nel file angular.json. Il template creato dalla CLI imposta i seguenti valori:
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
Qui trovate la documentazione completa di questa feature.
La configurazione di Typescript disponibile in un nuovo progetto è cambiata. Ora abbiamo un nuovo file chiamato tsconfig.base.json che si va ad aggiungere a tsconfig.json, tsconfig.app.json e tsconfig.spec.json. L’idea è quella di dare maggiore supporto agli editor e agli strumenti di build.
In questa versione il tsconfig.json si svuota e diventa solo un contenitore di percorsi di file:
/*
This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience.
It is not intended to be used to perform a compilation.
To learn more about this file see: https://angular.io/config/solution-tsconfig.
*/
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./e2e/tsconfig.json"
}
]
}
Il termine “Solution Style”, utilizzato nel commento, fa pensare a IDE classici per lo sviluppo di codice. Sarà quindi interessante vedere se il team di Angular si spingerà verso questa strada. In questa configurazione, il codice applicativo (gestito da tsconfig.app.json) viene chiaramente isolato da quello di testing (gestito da tsconfig.spec.json). La maggior parte del codice di configurazione è scritta in tsconfig.base.json (dove notiamo l’opzione strict di cui abbiamo parlato in precedenza). Tutti i percorsi e i file da includere sono nel file tsconfig.app.json.
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
Passando ad altro, è stata aggiornata la lista dei browser disponibile nel file (.browserslistrc) escludendo quelli più vecchi e i meno usati.
- last 1 Chrome version
- last 1 Firefox version
- last 2 Edge major versions
- last 2 Safari major version
- last 2 iOS major versions
- Firefox ESR
- not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the ‘not’ prefix on this line.
- not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the ‘not’ prefix on this line.
Lanciando il comando npx browserslist
è possibile visualizzare l’intera lista
- chrome 83
- edge 83
- edge 81
- firefox 78
- firefox 68
- ios_saf 13.4-13.5
- ios_saf 13.3
- ios_saf 13.2
- ios_saf 13.0-13.1
- ios_saf 12.2-12.4
- ios_saf 12.0-12.1
- safari 13.1
- safari 13
- safari 12.1
- safari 12
Vi aspettavate una lista più lunga? L’effetto collaterale più importante è che le build ES5 sono disabilitate per default. Per abilitare la build ES5 su un browser più vecchio (ad esempio Internet Explorer), bisogna prima aggiornare il file .browserlistrc.
Le classi che usano feature di Angular senza specificare un decoratore (ad esempio @Component, @Directive, @Module) non compilano con Ivy. Consideriamo ad esempio il seguente snippet di codice (fonte)
class Base {
@Input()
foo: string;
}
@Directive(...)
class Dir extends Base {
ngOnChanges(): void {}
}
Quando su una classe manca un decoratore Angular, il compilatore Ivy non aggiunge tutto il codice extra per la dependency injection. Come descritto nella documentazione ufficiale, la classe figlia erediterà da quella padre un costruttore senza tutte le necessarie informazioni aggiuntive e quindi quando Angular proverà a crearne un’istanza fallirà.
Nel View Engine, il compilatore ha una serie di dati globali e può quindi recuperare le informazioni mancanti. Ma il compilatore Ivy processa ciascuna direttiva in isolamento (per essere più veloce) e quindi gli manca un supporto globale che possa venirgli in aiuto.
Ci sono grosse novità riguardo il supporto a Bazel, uno strumento di build usato in Google che è stato reso disponibile in preview in Angular da più di un anno. La notizia è che Bazel non sarà MAI lo strumento di build in Angular e che il progetto è stato completamente accantonato. Un articolo di Alex Eagle spiega nel dettaglio quanto avvenuto. Trovo significativa la frase “Le app di Angular non soffrono dei problemi che Bazel cerca di risolvere” e che quindi è inutile introdurre nuova complessità e strumenti nel processo di build per non ottenere miglioramenti significativi.
L’ Angular Package Format utilizzato non solo nei pacchetti aggiuntivi (ad esempio Angular Material) ma anche in quelli contenuti nell’intero namespace @angular (ad esempio @angular/core, @angular/forms etc) è stato svuotato di tutti i bundle che prima erano necessari per la compilazione ES5. Ciò comporta una riduzione di 119 MB nel download innescato da npm install.
Cos’altro resta rispetto alla versione 9? La risoluzione di oltre 700 issue relative non solo alla libreria base ma anche alla parte di tooling (la ng cli). Il link che descrive come migrare un’applicazione alla versione 10 è il seguente.
In conclusione, la nuova versione di Angular non introduce sconvolgimenti nel mondo della programmazione JavaScript ma va semplicemente a stabilizzare e ottimizzare un framework che oramai possiamo considerare maturo. Cosa succederà nei prossimi anni è davvero impossibile da prevedere (come ci insegna questo 2020): subentrerà un nuovo attore a rovesciare l’egemonia di React e Angular (Vue pare già dimenticato)? WebAssembly sarà determinante nel futuro dei framework web? Vedremo!
Alla prossima!