facebook

Blog

Resta aggiornato

Facciamo il punto sullo stato attuale di Angular
Angular: presente e futuro
lunedì 28 Marzo 2022

Torniamo a parlare di Angular dopo un po’ di tempo dall’ultimo articolo sulle novità del framework. Secondo i sondaggi condotti da Stack Overflow, Angular resta il secondo framework di front-end più popolare alle spalle di React, anche se il divario è cresciuto rispetto al 2020. Altro aspetto peculiare è che non sembra particolarmente amato dai suoi utenti (addirittura quarto).  

Sembra che la sua stabilità, invece che incrementarne l’adozione, ne stia spegnendo l’entusiasmo: come se un framework completo, ma troppo “opinionated”, non sia più sufficiente. E’ un paradosso che sta spingendo il team di Angular a riconsiderare alcune scelte storiche come vedremo nel paragrafo finale. Nel frattempo vediamo cos’è successo nel 2021. 

Nel corso del 2021, sono state pubblicate due major release (la v.12 e v.13). Si è anzitutto concluso il periodo di transizione, avviato con la pubblicazione della versione 9 dove era stato inserito Ivy, per la nuova pipeline di compilazione e rendering. Quella usata precedentemente, nota come View Engine, è stata deprecata a partire dalla versione 12 (senza che fosse necessario alcun intervento nel codice): le librerie dipendenti da essa hanno continuato a funzionare grazie a un compilatore speciale chiamato ngcc. A partire dalla versione 13, però, View Engine non è più disponibile.  

Ci sono voluti anni per completare Ivy e i risultati sono evidenti anche nel caso di piccole applicazioni: Angular è diventato più semplice, veloce e facile da manutenere grazie al type checking, le ottimizzazioni di build e la fast change detection (di cui abbiamo parlato qui).  

Angular Package Format 

Sono state portate modifiche ad APF (Angular Package Format). Di cosa si tratta? E’ una specifica per la struttura e formato dei pacchetti npm che viene usata anzitutto dalle librerie native (ad esempio @angular/core oppure @angular/material) ma anche da librerie di terze parti. Nel frastagliato panorama di JavaScript, gli sviluppatori consumano pacchetti in tante maniere diverse: alcuni usano SystemJS, altri WebPack oppure potrebbero consumare pacchetti in Node o forse nel browser come bundle UMD, o attraverso variabili di accesso globale.  Questa proliferazione è dovuta al fatto che per molto tempo JavaScript non ha avuto una maniera ufficiale di importare / esportare moduli di codice.  

APF supporta tutti gli strumenti di sviluppo e workflow più comunemente usati ponendo enfasi sulle ottimizzazioni (dimensioni più piccole delle applicazioni e tempi di compilazione più ridotti). A questo indirizzo trovate le specifiche, mentre nell’immagine seguente trovate uno stralcio del pacchetto @angular/core. 

Angular Dev Tools

Sullo store Chrome Web è disponibile una estensione del browser Chrome che offre agli sviluppatori Angular nuovi strumenti sia per il debug che per il profiling delle applicazioni. Dopo l’installazione diventa disponibile un tab chiamato Angular nei Chrome Dev Tools. 

L’ estensione presenta a sua volta due tab aggiuntivi: Components e Profiler. Il primo consente di esplorare le componenti e direttive, vederne un’anticipazione o modificare lo stato. Il secondo offre uno sguardo sull’esecuzione della change detection di Angular. 

Nell’immagine, è possibile vedere una sequenza di barre, ciascuna delle quali simboleggia i cicli della change detection. Più alta è una barra, più lungo è il tempo speso in quel ciclo. Ogni barra ha un dettaglio che mostra anche la gerarchia dei genitori delle direttive selezionate. 

Testing in Angular

Protractor è un framework per il test end-to-end in applicazioni Angular. Il suo futuro è incerto e a partire dalla versione 12 non è più incluso nei nuovi progetti. L’idea è fornire supporto a soluzioni di terze parti come Cypress, WebdriverIO e TestCafe.

Rimanendo nell’ambito dei test, ricordiamo che Angular offre nei suoi progetti Jasmine, un framework JavaScript per la scrittura ed esecuzione di unit ed integration test. Esiste una guida ufficiale e completa su come il team di Angular pensa che debbano essere testati componenti, moduli, servizi, pipe e direttive. Vi rimandiamo anche a un articolo del nostro Adolfo per una discussione introduttiva sull’argomento. 

Nella versione 13 di Angular sono stati introdotti miglioramenti alla classe TestBed che impattano sul consumo di memoria e sulla velocità di esecuzione di ogni test eseguito. Il dettaglio è descritto nella seguente serie di articoli

Nullish Coalescing 

L’operatore ?? che da molto tempo è utilizzato dagli sviluppatori TypeScript, è stato messo a disposizione anche nei template Angular a partire dalla versione 12. Quindi mentre finora era necessario scrivere codice come il seguente nei template: 

{{age !== null && age !== undefined ? age : calculateAge() }} 

ora è possible invece scrivere con ovvio beneficio:  

{{ age ?? calculateAge() }} 

Creazione dinamica dei componenti

Ivy offre dei vantaggi agli sviluppatori anche in fase di scrittura del codice. A partire dalla versione 13 è stata semplificata l’API per la creazione dinamica di un componente (in questo articolo avevo introdotto il problema). Non è più necessaria l’iniezione di ComponentFactoryResolver e quindi un componente può essere instanziato con ViewContainerRef.createComponent senza dover creare una factory associata. Quindi, con la nuova API, possiamo scrivere ad esempio:

@Directive({ … }) 
export class MyDirective { 
    constructor(private viewContainerRef: ViewContainerRef) {} 
    createMyComponent() { 
        this.viewContainerRef.createComponent(MyComponent); 
    } 
} 

Addio IE11 

IE11 è stato definitivamente abbandonato. La rimozione permette ad Angular non solo di sfruttare le funzionalità dei moderni browser (ad esempio le variabili CSS e le web animation) ma anche di produrre applicazioni più piccole e veloci a causa della rimozione dei polyfills specifici di Internet Explorer. 

La ng CLI 

Il comando ng build, a partire dalla versione 12, funziona di default su production. Inoltre la CLI ha abilitato di default lo strict mode, che permette di scoprire errori sin dalle prime fasi di sviluppo. Qui troverete il dettaglio su questa modalità.  

Anche il language service è stato spostato di default ad uno basato su Ivy. Di cosa si tratta? Stiamo parlando del servizio utilizzato dagli editor e IDE per il controllo degli errori e l’ autocompletamento delle istruzioni. La CLI ora supporta l’uso di una cache di build persistente. Grazie ad essa si ha un miglioramento del 68% nella velocità di build. Questa configurazione (descritta nel dettaglio qui) è inserita all’interno del file angular.json 

{ 
    "$schema": "...", 
    "cli": { 
        "cache": { 
            "enabled": true, 
            "path": ".cache", 
            "environment": "all" 
        } 
    } 
    ... 
} 

Aggiornamento delle dipendenze

Quando creiamo una nuova app col comando ng new utilizziamo adesso la versione 7.4 di RxJS.  La libreria delle estensioni reattive di JavaScript è usata praticamente ovunque in Angular e quindi stiamo parlando di una dipendenza assolutamente fondamentale. 

RxJs 7 è più piccolo di RxJs 6: passiamo quindi da 52 KB a soli 19KB. È stato fatto un importante refactor del codice. 

L’operatore toPromise è stato deprecato e verrà completamente rimosso nella versione 8. Al suo posto sono stati introdotti firstValueFrom() e lastValueFrom(). L’operatore firstValueFrom() risolve la promise sul primo valore di un observable prima di eseguire l’unsubscribe. L’operatore lastValueFrom, invece, aspetta l’ultimo valore prima di risolvere la promise. Quindi, mentre in passato avevamo che 

const numbers$ = of(1,2,3,4,5) 
// toPromise() will return the last value of the observable 
numbers$.toPromise().then(n => console.log("toPromise(): " + n)) 

adesso possiamo scegliere 

// lastValueFrom() will return the last value just like to promise 
lastValueFrom(numbers$).then(n => console.log("lastValueFrom(): " + n)) 

 // firstValueFrom() will return the first value of the observable 
firstValueFrom(numbers$).then(n => console.log("firstValueFrom(): " + n))  

Attenzione che diversi operatori sono stati rinominati (ad esempio zip è diventato zipWith), altri deprecati e nuovi aggiunti. Qui troverete il dettaglio. 

E il futuro? 

Sono due le principali richieste fatte dagli utenti e che sono in fase di elaborazione da parte del team di Angular. La roadmap ufficiale è presente al seguente indirizzo

La prima è quella dei Strictly Typed Reactive Form (qui troverete la RFC ufficiale). Prendiamo ad esempio un form, popolato con un valore default. 

const partyForm = new FormGroup({ 
  address: new FormGroup({
                  house: new FormControl(1234), 
                  street: new FormControl('Powell St')}), 
                  formal: new FormControl(false), 
                  foodOptions: new FormArray([]) 
}); 

Quando proviamo ad interagire col form e leggere i suoi elementi, ci troviamo frequentemente ad avere dei valori di tipo any. 

const partyDetails = partyForm.getRawValue(); // type `any` 
const where = partyForm.get('address.street')!.value; // type `any` 
partyForm.controls.formal.setValue(true); // param has type `any` 

Immaginiamo di avere un type corrispondente:

type Party = { 
  address: { 
      house: number,   
      street: string, 
  }, 
  formal: boolean, 
  foodOptions: Array<{ 
    food: string, 
    price: number, 
  }> 
} 

Con la proposta in fase di pubblicazione avremo che gli elementi del form sono completamenti tipizzati. Ad esempio: 

const partyDetails = partyForm.getRawValue(); // a `Party` object 
const where = partyForm.get('address.street')!.value; // type `string` 
partyForm.controls.formal.setValue(true); // param has type `boolean` 

La seconda richiesta è più radicale (qui ne trovate il dettaglio): rendere i moduli opzionali. 

Gli NgModule sono il concetto base di una applicazione Angular. Persino un’app Hello World ne ha bisogno di uno per la sua esecuzione. I moduli sono l’unità di riutilizzo, le librerie sono moduli, il lazy-loading è basato sui moduli (qui ne parla sempre il nostro Adolfo).  

Poco prima, però, abbiamo parlato della creazione dinamica di un’istanza di un componente. Tale funzionalità nasconde delle insidie e non è detto che a runtime funzioni correttamente. Un componente può aspettarsi che all’invocazione del suo costruttore venga passato un servizio. Tipicamente è un modulo che garantisce che ciò avvenga correttamente ma se istanzio direttamente un componente rischio di non avere il provider necessario a runtime. Quindi, se ci pensate (ed è l’unico esempio nella scena dei framework web), i componenti hanno bisogno dei moduli che sono quindi il blocco più piccolo di codice riutilizzabile. 

Nelle prossime versioni di Angular potremo scegliere se utilizzare o meno gli NgModule. Uno standalone component (o pipe o directive) non sarà dichiarato in alcun modulo e gestirà autonomamente le proprie dipendenze e potrà essere usato direttamente senza aver bisogno di un NgModule intermedio. Ecco un semplice esempio: 

import {Component} from '@angular/core'; 
@Component({ 
  standalone: true,   
  template: `I'm a standalone component!` 
}) 
export class HelloStandaloneComponent {} 

Come gestiremo le dipendenze? Verrà aggiunta una proprietà imports all’interno del decoratore @Component dove andremo a specificare le dipendenze presenti nel template 

import {Component} from '@angular/core'; 
import {FooComponent, BarDirective, BazPipe} from './template-deps'; 

 @Component({ 
  standalone: true, 
  imports: [FooComponent, BarDirective, BazPipe], 
  template: ` 
    <foo-cmp></foo-cmp> 
    <div bar>{{expr | baz}}</div> 
  ` 
}) 
export class ExampleStandaloneComponent {} 

Conclusioni 

Il team di Angular ha quindi accettato la sfida di provare a fornire, a chi voglia utilizzarlo, una strada alternativa rispetto a quanto visto finora.  Nel frattempo, TypeScript e RxJS continuano a renderlo un framework estremamente solido per lo sviluppo di soluzioni front-end. Sono queste le uniche dipendenze imprescindibili del framework e ciò fa sì che i team di sviluppo non accumulino debito tecnico seguendo librerie di terze parti anche per problemi comuni quali la gestione dei form o delle chiamate HTTP. TypeScript ormai è utilizzato anche nell’ecosistema React. RxJS, invece, resta lo scoglio teorico più difficile da superare per un team che inizi ad utilizzare Angular, ma, a mio modo di vedere, è il vero valore aggiunto di questo framework. 

Alla prossima.