Decorators: sono funzioni che ritornano altre funzioni e indicano ad Angular cosa deve fare quel pezzo di codice. Sono caratterizzati dal simbolo @ e sono invocati a runtime con gli argomenti passati (dependency injection).
I più frequenti sono @NgModule(), @Pipe(), @Directive(), @Input(), @Output().
Un modulo rappresenta un gruppo di files che lavorano insieme, ad esempio un componente, un servizio e una pipe. Dalla versione 14 è possibile utilizzare gli Standalone Components, cioè componenti che non fanno parte di un modulo.
Il decorator @NgModule() prende come parametro un oggetto di configurazione, che ha la proprietà declarations, un array di tutti i componenti, directives e pipes che fanno parte del modulo.
La proprietà imports invece contiene gli altri moduli che vogliamo utilizzare all'interno nostro modulo, ad esempio AppRoutingModule, BrowserModule, CommonModule che contiene built-in pipes e directives, HttpClientModule che ci da accesso al servizio HttpClientService.
La proprietà exports contiene tuto ciò che del nostro modulo vogliamo rendere disponibile agli altri moduli.
La proprietà providers contiene tutto il codice che vogliamo rendere disponibile attraverso la dependency injection. Tipicamente si inseriscono i moduli nell'array degli imports e i servizi nell'array dei providers.
Nell'array dei providers si possono inserire direttamente i nomi delle classi oppure un oggetto con le proprietà provide e useClass; utilizzando invece useValue si possono inserire altre cose come valori primitivi o oggetti, esistono anche useFactory e useExisting.
Infine c'è la proprietà bootstrap che sta ad indicare il componente che deve essere caricato quando viene lanciata l'applicazione (lanciando il comando npm start) e quindi questa proprietà viene definita solo nel root module.
Il root module viene poi importato nel file main.ts e viene inserito come parametro della funzione bootstrapModule(), la quale può contenere anche un secondo parametro di configurazione che serve per definire dei settings globali.
I Components sono composti di quattro parti: un template HTML, gli stili CSS, una classe Typescript che ne definisce il comportamento e il selettore del componente che specifica dove inserire il componente nel DOM, il Component decorator ha quindi come parametro un oggetto di configurazione che contiene le proprietà selector (includere sempre un trattino nel nome del selettore), templateUrl e styleUrls.
Nel template HTML possono essere usati tutte le proprietà public o protected della classe, gli observables si indicano per convenzione con un $ finale.
Nel costruttore facciamo la dependency injection, ad esempio di un servizio, e possiamo mettere del codice di inizializzazione.
Nel template quando troviamo un nome che inizia con il simbolo # si tratta di una Template Reference Variable, questo è un modo per riutilizzare una parte di codice all'interno dello stesso template.
Ci sono tre tipi di binding in Angular: event, property e interpolation.
L'Event binding ci permette di assegnare azioni agli eventi del DOM come keyup o click, utilizziamo delle parentesi tonde e dentro mettiamo il nome dell'evento al verificarsi del quale vogliamo eseguire una certa azione.
Il Property binding ci permette di assegnare valori agli attributi di un componente, si utilizza inserendo tra parentesi quadre il nome dell'attributo o della proprietà.
L'Interpolation binding ci permette di mostrare dinamicamente del contenuto nell'interfaccia inserendo le variabili all'interno delle doppie parentesi graffe.
Le Structural Directives aggiungono o rimuovono parti del DOM, iniziano sempre con un asterisco.
*ngIf aggiunge o rimuove una parte del DOM in base ad un valore booleano, si può anche aggiungere un else per specificare cosa mostrare quando il valore del booleano è falso, che fa riferimento al contenuto di un <ng-template>.
Un <ng-template> specifica un elemento del DOM che non viene caricato di default ma solo se la condizione dell'*ngIf è falsa.
*ngFor serve per iterare su un array utilizzando il costrutto let/of.
Per rendere un componente Standalone basta aggiungere nell'oggetto di configurazione passato al decorator una proprietà standalone: true, poi si possono aggiungere gli imports e i providers come si sarebbe fatto in un modulo.
Gli Standalone components possono poi essere importati dentro altri moduli nell'array degli imports.
Le pipes servono per manipolare la visualizzazione dei dati nel template, ad esempio per rendere il testo in maiuscolo o per la localizzazione di date e numeri, e possono anche essere applicate più pipes in sequenza. Sono identificate da un simbolo | che precede il nome della pipe.
Le pipes sono pure functions, cioè ritornano sempre lo stesso output per lo stesso input, e vengono eseguite solo se ci sono cambiamenti in valori primitivi, ignorano i cambiamenti dei dati all'interno di oggetti o array. Si può cambiare questo comportamento cambiando la pipe in una impure function ma è sconsigliato.
Ci sono molte pipes built-in, ad esempio la pipe slice per scegliere di mostrare solo una parte degli item di un array su cui si sta facendo un *ngFor. Alla pipe slice bisogna passare due argomenti, l'indice di partenza e il numero di elementi da mostrare e si fa utilizzando il simbolo due punti.
Si possono creare delle pipes custom usando il decorator @Pipe(), che prende come argomento un oggetto di configurazione che ha le proprietà name (non usare mai i trattini nel nome ma sempre camelCase), pure (si consiglia di mettere sempre true) e standalone (obbligatorio metterla a true se nel progetto vengono usati Standalone Components).
Ogni pipe deve avere il metodo transform() che prende come parametro il valore che si vuole trasformare e bisogna sempre specificare un valore di ritorno.
La pipe async serve per accedere a dati che arrivano da promises o observables.
I servizi sono un modo per riutilizzare il codice all'interno dell'app e servono per fare cose che non vanno a modificare l'interfaccia, come ad esempio prendere i dati dal server.
Il decorator per i servizi si chiama @Injectable() e prende come parametro un oggetto di configurazione che ha la proprietà providedIn, la quale indica quale parte del progetto ha accesso al servizio. Il valore di default è null, questo significa che il servizio deve essere per forza incluso in un modulo. Se invece si utilizza providedIn: 'root' il servizio viene istanziato una volta per tutta l'app (è un singleton).
Tipicamente in un servizio nel costruttore si passa l'HttpClient in modo da poter fare chiamate http al server.
E' buona norma quando si fanno delle chiamate con il metodo GET indicare anche il tipo del valore che sarà ritornato.
Esiste un metodo pipe() built-in che si può concatenare al metodo get dell'HttpClient e che serve per lavorare con gli observables. Consente di trasformare i dati utilizzando la composizione di funzioni.
Ad esempio dentro il metodo pipe() si può chiamare il metodo delay() che simula un ritardo nell'arrivo dei dati, oppure il metodo map che serve per eseguire delle azioni sui dati passando una callback come parametro.
Le Directives servono per aggiungere dei comportamenti agli elementi nel template. Il decorator @Directive() prende come parametro un oggetto di configurazione che ha la proprietà selector, che indica un selettore da utilizzare nel template per identificare la direttiva nel template. L'oggetto di configurazione ha poi i parametri providers e standalone.
Uno degli import più frequenti nelle direttive è ElementRef, che da accesso all'elemento HTML in cui è applicata la direttiva.
Esistono due files di configurazione in un progetto Angular: tsconfig.json e angular.json. Il primo definisce come il codice TypeScript debba essere trasformato in codice JavaScript, il secondo indica alla CLI come configurare e buildare il progetto.
Nella cartella environments sono contenuti files che definiscono i diversi ambienti (produzione, sviluppo ecc...) in modo da poter customizzare l'app in base all'ambiente indicando ad esempio l'url del server per ogni ambiente.
Usare la Dependency Injection significa dichiarare nel costruttore della classe come parametro il servizio (o altro) che vogliamo utilizzare, indicandolo con una proprietà tipizzata con la classe che vogliamo iniettare.
Il termine Change Detection indica come Angular aggiorna la UI in base ai cambiamenti dei dati rendendo l'app dinamica e interattiva. Esistono due strategie per gestire la change detection: default e OnPush. La startegia può essere diversa in ogni componente, basta indicarla nell'oggetto di configurazione del decorator con la proprietà changeDetection. La strategia di default consiste nell'aggiornare la UI al cambiamento dei dati negli oggetti e array, quando vengono ridefinite proprietà nella classe del componente, o al verificarsi di eventi. La strategia OnPush aggiorna la UI solo quando viene ridefinita una proprietà, al verificarsi di un evento o quando viene manualmente chiamata la changeDetection.
I lifecycle hooks sono dei particolari metodi che vengono eseguiti durante il ciclo di vita del componente e che noi possiamo ridefinire inserendo il nostro codice, che quindi verrà eseguito in specifici momenti. E' buona norma indicare nella classe quali lifecycle hooks si stanno utilizzando tramite la keyword implements.
Dato che una app in Angular è costituita da una sola pagina possiamo usare il Router per definire delle diverse url nelle quali caricare diverse parti dell'app.
Nel AppRoutingModule, il modulo nel quale importeremo il RouterModule di Angular, è presente un array di Routes, cioè di oggetti che definiscono ciascuna rotta. Ogni oggetto ha la proprietà path, che indica l'url che vedremo nel browser, sempre relativo a quanto indicato nel tag <base href> dell'index.html, loadComponent che indica quale componente deve essere caricato, title che indica il titolo che si vede nella linguetta del browser, e altre proprietà come loadChildren e data.
Negli imports e negli exports dell'AppRoutingModule dobbiamo inserire il RouterModule fornito da Angular, in particolare negli imports dobbiamo inserire il metodo statico del RouterModule chiamato forRoot (esiste anche forChild) che specifica che si tratta della definizione principale delle rotte dell'app, può essere usato una sola volta nell'app e ha come primo parametro l'array di Routes con tutti i path. Come secondo parametro prende un oggetto di configurazione. Useremo il metodo statico forChild per definire le rotte specifiche per ciascun modulo.
Con l'architettura a moduli all'avvio dell'app Angular carica il modulo principale che serve per la pagina principale, e intanto in background comincia a caricare i moduli che potranno servire più avanti (lazy loading), ma si può personalizzare la preloadingStrategy nell'oggetto di configurazione del metodo statico forRoot: il valore di default è NoPreloading, che significa che ogni modulo verrà caricato solo quando serve, oppure si può settare PreloadAllModules oppure usare un servizio custom indicando preloadingStrategy: PreloadingService.
Per fare in modo che ad ogni rotta caricata la vista sia aggiornata con il componente corrispondente dobbiamo mettere nel template html dell'app component il tag <router-outlet></router-outlet>.
Per navigare da una rotta all'altra usiamo la direttiva routerLink all'interno di un tag <a></a> passando come valore il path desiderato oppure un array di segmenti di path.
A patire dalla versione 16 Angular ha introdotto i signals, cioè valori che cambiano nel tempo e in relazione a questi cambiamenti viene aggiornata la vista.
Usando la funzione signal() passando come parametro un valore iniziale creiamo una variabile che muterà nel tempo. Questa variabile avrà dei metodi utili come set, get, update o mutate.
Il metodo computed() serve per creare un nuovo signal a partire da un altro, questo nuovo signal sarà read-only e Angular aggiornerà il suo valore solo quando la funzione verrà chiamata.
La funzione effect, chiamata nel costruttore, serve per reagire ai cambiamenti di valore del signal.
Con il comando ng-test possiamo eseguire i test fatti con Jasmine e Karma.