Tutti i vantaggi, nessun problema: un tutorial di Angular 9

Pubblicato: 2022-03-11

"Ogni anno Internet si interrompe", dice il proverbio, e gli sviluppatori di solito devono andare a sistemarlo. Con la tanto attesa versione 9 di Angular, si potrebbe pensare che ciò si applichi e le app sviluppate su versioni precedenti dovrebbero passare attraverso un importante processo di migrazione.

Ma non è così! Il team di Angular ha completamente riprogettato il suo compilatore, ottenendo build più veloci, esecuzioni di test più veloci, dimensioni dei bundle più piccole e, soprattutto, compatibilità con le versioni precedenti. Con Angular 9, gli sviluppatori ottengono praticamente tutti i vantaggi senza problemi.

In questo tutorial di Angular 9, creeremo un'applicazione Angular da zero. Utilizzeremo alcune delle ultime funzionalità di Angular 9 e esamineremo altri miglioramenti lungo il percorso.

Tutorial Angular 9: iniziare con una nuova applicazione angolare

Iniziamo con il nostro esempio di progetto Angular. Innanzitutto, installiamo l'ultima versione della CLI di Angular:

 npm install -g @angular/cli

Possiamo verificare la versione Angular CLI eseguendo ng version .

Quindi, creiamo un'applicazione Angular:

 ng new ng9-app --create-application=false --strict

Stiamo usando due argomenti nel nostro ng new :

  • --create-application=false indicherà alla CLI di generare solo file dell'area di lavoro. Questo ci aiuterà a organizzare meglio il nostro codice quando avremo bisogno di più di un'app e più librerie.
  • --strict aggiungerà regole più rigorose per imporre più digitazione TypeScript e pulizia del codice.

Di conseguenza, abbiamo una cartella e file dell'area di lavoro di base.

Uno screenshot di un IDE che mostra la cartella ng9-app, contenente node_modules, .editorconfig, .gitignore, angular.json, package-lock.json, package.json, README.md, tsconfig.json e tslint.json.

Ora aggiungiamo una nuova app. Per farlo, eseguiremo:

 ng generate application tv-show-rating

Ci verrà richiesto:

 ? Would you like to share anonymous usage data about this project with the Angular Team at Google under Google's Privacy Policy at https://policies.google.com/privacy? For more details and how to change this setting, see http://angular.io/analytics. No ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? SCSS

Ora, se eseguiamo ng serve , vedremo l'app in esecuzione con il suo scaffolding iniziale.

Uno screenshot dell'impalcatura di Angular 9, con un avviso che "l'app di valutazione dei programmi TV è in esecuzione!" Ci sono anche collegamenti a risorse e passaggi successivi.

Se eseguiamo ng build --prod , possiamo vedere l'elenco dei file generati.

Uno screenshot dell'output "ng build --prod" di Angular 9. Inizia con "Generazione di bundle ES5 per il caricamento differenziale..." Al termine, elenca diversi blocchi di file JavaScript (runtime, polyfills e main, ciascuno con una versione -es2015 e -es5) e un file CSS. La riga finale fornisce un timestamp, hash e un tempo di esecuzione di 23.881 millisecondi.

Abbiamo due versioni di ogni file. Uno è compatibile con i browser legacy e l'altro è compilato per ES2015, che utilizza API più recenti e richiede meno polyfill per essere eseguito sui browser.

Un grande miglioramento di Angular 9 è la dimensione del pacchetto. Secondo il team di Angular, puoi vedere una diminuzione fino al 40% per le grandi app.

Per un'app appena creata, la dimensione del pacchetto è abbastanza simile a quella di Angular 8, ma man mano che la tua app cresce, vedrai la dimensione del pacchetto diventare più piccola rispetto alle versioni precedenti.

Un'altra funzionalità introdotta in Angular 9 è la possibilità di avvisarci se uno qualsiasi dei file CSS in stile componente è più grande di una soglia definita.

Uno screenshot della sezione "budget" di un file di configurazione JSON di Angular 9, con due oggetti in un array. Il primo oggetto ha "type" impostato su "initial", "maximumWarning" impostato su "2mb" e "maximumError" impostato su "5mb". Il secondo oggetto ha "type" impostato su "anyComponentStyle", "maximumWarning" impostato su "6kb" e "maximumError" impostato su "10kb".

Questo ci aiuterà a rilevare le importazioni di stile errato o file di stile di componenti di grandi dimensioni.

Aggiunta di un modulo per valutare i programmi TV

Successivamente, aggiungeremo un modulo per valutare i programmi TV. Per questo, per prima cosa, installeremo bootstrap e ng-bootstrap :

 npm install bootstrap @ng-bootstrap/ng-bootstrap

Un altro miglioramento su Angular 9 è i18n (internazionalizzazione). In precedenza, gli sviluppatori avrebbero dovuto eseguire una build completa per ogni locale in un'app. Angular 9 invece ci consente di creare un'app una volta e generare tutti i file i18n in un processo di post-compilazione, riducendo significativamente i tempi di costruzione. Poiché ng-bootstrap ha una dipendenza da i18n, aggiungeremo il nuovo pacchetto al nostro progetto:

 ng add @angular/localize

Successivamente, aggiungeremo il tema Bootstrap al styles.scss della nostra app:

 @import "~bootstrap/scss/bootstrap";

E includeremo NgbModule e ReactiveFormsModule nel nostro AppModule su app.module.ts :

 // ... import { ReactiveFormsModule } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ imports: [ // ... ReactiveFormsModule, NgbModule ], })

Successivamente, aggiorneremo app.component.html con una griglia di base per il nostro modulo:

 <div class="container"> <div class="row"> <div class="col-6"> </div> </div> </div>

E genera il componente del modulo:

 ng gc TvRatingForm

Aggiorniamo tv-rating-form.component.html e aggiungiamo il modulo per valutare i programmi TV.

 <form [formGroup]="form" (ngSubmit)="submit()" class="mt-3"> <div class="form-group"> <label>TV SHOW</label> <select class="custom-select" formControlName="tvShow"> <option *ngFor="let tvShow of tvShows" [value]="tvShow.name">{{tvShow.name}}</option> </select> </div> <div class="form-group"> <ngb-rating [max]="5" formControlName="rating"></ngb-rating> </div> <button [disabled]="form.invalid || form.disabled" class="btn btn-primary">OK</button> </form>

E tv-rating-form.component.ts apparirà così:

 // ... export class TvRatingFormComponent implements OnInit { tvShows = [ { name: 'Better call Saul!' }, { name: 'Breaking Bad' }, { name: 'Lost' }, { name: 'Mad men' } ]; form = new FormGroup({ tvShow: new FormControl('', Validators.required), rating: new FormControl('', Validators.required), }); submit() { alert(JSON.stringify(this.form.value)); this.form.reset(); } }

Infine, aggiungiamo il modulo a app.component.html :

 <!-- ... --> <div class="col-6"> <app-tv-rating-form></app-tv-rating-form> </div>

A questo punto, abbiamo alcune funzionalità di base dell'interfaccia utente. Ora, se eseguiamo di nuovo ng serve , possiamo vederlo in azione.

Screencap di un'app tutorial di Angular 9 che mostra un modulo intitolato "Programma TV", con un elenco a discesa che elenca una manciata di titoli di programmi, un misuratore di stelle e un pulsante OK. Nell'animazione, l'utente seleziona uno spettacolo, seleziona una valutazione e quindi fa clic sul pulsante OK.

Prima di andare avanti, diamo una rapida occhiata ad alcune nuove interessanti funzionalità di Angular 9 che sono state aggiunte per aiutare il debug. Poiché questo è un compito molto comune nel nostro lavoro quotidiano, vale la pena sapere cosa è cambiato per rendere la nostra vita un po' più facile.

Debug con Angular 9 Ivy

Un altro grande miglioramento introdotto in Angular 9 e Angular Ivy è l'esperienza di debug. Il compilatore ora può rilevare più errori e lanciarli in un modo più "leggibile".

Vediamolo in azione. Innanzitutto, attiveremo il controllo del modello in tsconfig.json :

 { // ... "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictTemplates": true } }

Ora, se aggiorniamo l'array tvShows e rinominiamo il name in title :

 tvShows = [ { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' } ];

... riceveremo un errore dal compilatore.

Uno screenshot dell'output del compilatore Angular 9/Angular Ivy, con un nome file e una posizione, che dice "errore TS2339: La proprietà 'nome' non esiste sul tipo '{ titolo: stringa; }'." Mostra anche la riga di codice in questione e ne sottolinea il riferimento, in questo caso nel file tv-rating-form.component.html dove è menzionato tvShow.name. Successivamente, il riferimento a questo file HTML viene ricondotto al file TypeScript corrispondente ed evidenziato in modo simile.

Questo controllo del tipo ci consentirà di prevenire errori di battitura e l'utilizzo errato dei tipi TypeScript.

Convalida Ivy angolare per @Input()

Un'altra buona convalida che otteniamo è con @Input() . Ad esempio, potremmo aggiungere questo a tv-rating-form.component.ts :

 @Input() title: string;

...e collegalo in app.component.html :

 <app-tv-rating-form [title]="title"></app-tv-rating-form>

... e poi cambia app.component.ts in questo modo:

 // ... export class AppComponent { title = null; }

Se apportiamo queste tre modifiche, otterremo un altro tipo di errore dal compilatore.

Uno screenshot dell'output del compilatore Angular 9/Angular Ivy, in un formato simile al precedente, che evidenzia app.component.html con "errore TS 2322: il tipo 'null' non è assegnabile al tipo 'string'."

Nel caso in cui desideriamo bypassarlo, possiamo utilizzare $any() sul modello per eseguire il cast del valore su any e correggere l'errore:

 <app-tv-rating-form [title]="$any(title)"></app-tv-rating-form>

Il modo giusto per risolvere questo problema, tuttavia, sarebbe rendere il title sul modulo annullabile:

 @Input() title: string | null ;

The ExpressionChangedAfterItHasBeenCheckedError in Angular 9 Ivy

Uno degli errori più temuti nello sviluppo di Angular è ExpressionChangedAfterItHasBeenCheckedError . Per fortuna, Ivy restituisce l'errore in modo più chiaro, rendendo più facile trovare l'origine del problema.

Quindi, introduciamo un errore ExpressionChangedAfterItHasBeenCheckedError . Per fare ciò, per prima cosa, genereremo un servizio:

 ng gs Title

Successivamente, aggiungeremo un BehaviorSubject e metodi per accedere a Observable ed emettere un nuovo valore.

 export class TitleService { private bs = new BehaviorSubject < string > (''); constructor() {} get title$() { return this.bs.asObservable(); } update(title: string) { this.bs.next(title); } }

Successivamente, lo aggiungeremo a app.component.html :

 <!-- ... --> <div class="col-6"> <h2> {{title$ | async}} </h2> <app-tv-rating-form [title]="title"></app-tv-rating-form> </div>

E in app.component.ts , inietteremo il TitleService :

 export class AppComponent implements OnInit { // ... title$: Observable < string > ; constructor( private titleSvc: TitleService ) {} ngOnInit() { this.title$ = this.titleSvc.title$; } // ... }

Infine, in tv-rating-form.component.ts , inietteremo TitleService e aggiorneremo il titolo di AppComponent , che genererà un errore ExpressionChangedAfterItHasBeenCheckedError .

 // ... constructor( private titleSvc: TitleService ) { } ngOnInit() { this.titleSvc.update('new title!'); }

Ora possiamo vedere l'errore dettagliato nella console di sviluppo del browser e facendo clic su app.component.html ci indicherà dove si trova l'errore.

Una schermata della console di sviluppo del browser, che mostra la segnalazione di Angular Ivy dell'errore ExpressionChangedAfterItHasBeenCheckedError. Una traccia dello stack in testo rosso fornisce l'errore, insieme ai valori precedenti e correnti, e un suggerimento. Nel mezzo della traccia dello stack c'è l'unica riga che non si riferisce a core.js. L'utente fa clic su di esso e viene portato alla riga di app.component.html che causa l'errore.

Possiamo correggere questo errore avvolgendo la chiamata di servizio con setTimeout :

 setTimeout(() => { this.titleSvc.update('new title!'); });

Per capire perché si verifica l'errore ExpressionChangedAfterItHasBeenCheckedError ed esplorare altre possibilità, vale la pena leggere il post di Maxim Koretskyi sull'argomento.

Angular Ivy ci consente di presentare gli errori in modo più chiaro e aiuta a imporre la digitazione di TypeScript nel nostro codice. Nella sezione seguente, tratteremo alcuni scenari comuni in cui sfrutteremo Ivy e il debug.

Scrivere un test per la nostra app Angular 9 con cablaggi di componenti

In Angular 9 è stata introdotta una nuova API di test denominata cablaggio dei componenti . L'idea alla base è quella di rimuovere tutto il lavoro necessario per interagire con il DOM, rendendo molto più facile lavorare e più stabile da eseguire.

L'API del cablaggio dei componenti è inclusa nella libreria @angular/cdk , quindi installiamola prima sul nostro progetto:

 npm install @angular/cdk

Ora possiamo scrivere un test e sfruttare i cablaggi dei componenti. In tv-rating-form.component.spec.ts , impostiamo il test:

 import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { ReactiveFormsModule } from '@angular/forms'; describe('TvRatingFormComponent', () => { let component: TvRatingFormComponent; let fixture: ComponentFixture < TvRatingFormComponent > ; beforeEach(async (() => { TestBed.configureTestingModule({ imports: [ NgbModule, ReactiveFormsModule ], declarations: [TvRatingFormComponent] }).compileComponents(); })); // ... });

Quindi, implementiamo un ComponentHarness per il nostro componente. Creeremo due cablaggi: uno per TvRatingForm e un altro per NgbRating . ComponentHarness richiede un campo static , hostSelector , che dovrebbe assumere il valore del selettore del componente.

 // ... import { ComponentHarness, HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; class TvRatingFormHarness extends ComponentHarness { static hostSelector = 'app-tv-rating-form'; } class NgbRatingHarness extends ComponentHarness { static hostSelector = 'ngb-rating'; } // ...

Per il nostro TvRatingFormHarness , creeremo un selettore per il pulsante di invio e una funzione per attivare un click . Puoi vedere quanto diventa più facile implementarlo.

 class TvRatingFormHarness extends ComponentHarness { // ... protected getButton = this.locatorFor('button'); async submit() { const button = await this.getButton(); await button.click(); } }

Successivamente, aggiungeremo metodi per impostare una valutazione. Qui utilizziamo locatorForAll per cercare tutti gli elementi <span> che rappresentano le stelle su cui l'utente può fare clic. La funzione di rate ottiene solo tutte le possibili stelle di valutazione e fa clic su quella corrispondente al valore inviato.

 class NgbRatingHarness extends ComponentHarness { // ... protected getRatings = this.locatorForAll('span:not(.sr-only)'); async rate(value: number) { const ratings = await this.getRatings(); return ratings[value - 1].click(); } }

L'ultimo pezzo mancante è collegare TvRatingFormHarness a NgbRatingHarness . Per farlo, aggiungiamo semplicemente il localizzatore sulla classe TvRatingFormHarness .

 class TvRatingFormHarness extends ComponentHarness { // ... getRating = this.locatorFor(NgbRatingHarness); // ... }

Ora scriviamo il nostro test:

 describe('TvRatingFormComponent', () => { // ... it('should pop an alert on submit', async () => { spyOn(window, 'alert'); const select = fixture.debugElement.query(By.css('select')).nativeElement; select.value = 'Lost'; select.dispatchEvent(new Event('change')); fixture.detectChanges(); const harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, TvRatingFormHarness); const rating = await harness.getRating(); await rating.rate(1); await harness.submit(); expect(window.alert).toHaveBeenCalledWith('{"tvShow":"Lost","rating":1}'); }); });

Si noti che per la nostra select all'interno del modulo, non abbiamo implementato l'impostazione del suo valore tramite un'imbracatura. Questo perché l'API non supporta ancora la selezione di un'opzione. Ma questo ci dà la possibilità di confrontare qui come appariva l'interazione con gli elementi prima dei cablaggi dei componenti.

Un'ultima cosa prima di eseguire i test. Dobbiamo correggere app.component.spec.ts poiché abbiamo aggiornato il title in modo che sia null .

 describe('AppComponent', () => { // ... it(`should have as title 'tv-show-rating'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app.title).toEqual(null); }); });

Ora, quando eseguiamo ng test , il nostro test passa.

Uno screenshot di Karma che esegue i test sulla nostra app Angular 9. Mostra "Ran 2 of 6 specs" con il messaggio "Incomplete: fit() or fdescribe() was found, 2 specs, 0 failures, randomized with seed 69573." I due test di TvRatingFormComponent sono evidenziati. I tre test di AppComponent e quello di TitleService sono tutti grigi.

Torna alla nostra app di esempio Angular 9: salvataggio dei dati in un database

Concludiamo il nostro tutorial su Angular 9 aggiungendo una connessione a Firestore e salvando le valutazioni nel database.

Per farlo, dobbiamo creare un progetto Firebase. Quindi, installeremo le dipendenze richieste.

 npm install @angular/fire firebase

Nelle impostazioni del progetto di Firebase Console, otterremo la sua configurazione e le aggiungeremo a environment.ts e environment.prod.ts :

 export const environment = { // ... firebase: { apiKey: '{your-api-key}', authDomain: '{your-project-id}.firebaseapp.com', databaseURL: 'https://{your-project-id}.firebaseio.com', projectId: '{your-project-id}', storageBucket: '{your-project-id}.appspot.com', messagingSenderId: '{your-messaging-id}', appId: '{your-app-id}' } };

Successivamente, importeremo i moduli necessari in app.module.ts :

 import { AngularFireModule } from '@angular/fire'; import { AngularFirestoreModule } from '@angular/fire/firestore'; import { environment } from '../environments/environment'; @NgModule({ // ... imports: [ // ... AngularFireModule.initializeApp(environment.firebase), AngularFirestoreModule, ], // ... })

Successivamente, in tv-rating-form.component.ts , inietteremo il servizio AngularFirestore e salveremo una nuova valutazione all'invio del modulo:

 import { AngularFirestore } from '@angular/fire/firestore'; export class TvRatingFormComponent implements OnInit { constructor( // ... private af: AngularFirestore, ) { } async submit(event: any) { this.form.disable(); await this.af.collection('ratings').add(this.form.value); this.form.enable(); this.form.reset(); } } 

Uno screencap di un'app tutorial di Angular 9 che mostra un modulo intitolato "TV SHOW" sotto il titolo di una pagina più grande "nuovo titolo!" Anche in questo caso, ha un elenco a discesa che elenca una manciata di titoli di spettacoli, un misuratore di stelle e un pulsante OK, e di nuovo l'utente seleziona uno spettacolo, seleziona una valutazione e quindi fa clic sul pulsante OK.

Ora, quando andiamo alla Firebase Console, vedremo l'elemento appena creato.

Uno screenshot della console Firebase. Nella colonna di sinistra c'è joaq-lab con alcune raccolte: partecipanti, gare, valutazioni, test e utenti. L'elemento di valutazione è selezionato ed è presente nella colonna centrale con un ID selezionato: è l'unico documento. La colonna di destra mostra due campi: "rating" è impostato su 4 e "tvShow" è impostato su "Mad men".

Infine, elenchiamo tutte le valutazioni in AppComponent . Per fare ciò, in app.component.ts otterremo i dati dalla raccolta:

 import { AngularFirestore } from '@angular/fire/firestore'; export class AppComponent implements OnInit { // ... ratings$: Observable<any>; constructor( // ... private af: AngularFirestore ) { } ngOnInit() { // ... this.ratings$ = this.af.collection('ratings').valueChanges(); } }

…e in app.component.html aggiungeremo un elenco di valutazioni:

 <div class="container"> <div class="row"> // ... <div class="col-6"> <div> <p *ngFor="let rating of ratings$ | async"> {{rating.tvShow}} ({{rating.rating}}) </p> </div> </div> </div> </div>

Ecco come appare la nostra app tutorial Angular 9 quando è tutto messo insieme.

Uno screencap di un'app tutorial di Angular 9 che mostra un modulo intitolato "TV SHOW" sotto il titolo di una pagina più grande "nuovo titolo!" Ancora una volta, ha un menu a discesa che elenca una manciata di titoli di programmi, un misuratore di stelle e un pulsante OK. Questa volta, una colonna di destra elenca già "Mad men (4)" e l'utente valuta Lost a tre stelle, seguito di nuovo da "Mad men" a quattro stelle. La colonna di destra rimane in ordine alfabetico dopo entrambe le nuove valutazioni.

Angular 9 e Angular Ivy: migliore sviluppo, migliori app e migliore compatibilità

In questo tutorial di Angular 9, abbiamo trattato la creazione di un modulo di base, il salvataggio dei dati su Firebase e il recupero di elementi da esso.

Lungo la strada, abbiamo visto quali miglioramenti e nuove funzionalità sono inclusi in Angular 9 e Angular Ivy. Per un elenco completo, puoi controllare l'ultimo post di rilascio del blog ufficiale di Angular.


Badge Partner Google Cloud.

In qualità di Google Cloud Partner, gli esperti certificati da Google di Toptal sono a disposizione delle aziende on demand per i loro progetti più importanti.