back to top

Come caricare e avviare un’applicazione Angular

Abbiamo concluso la precedente lezione mostrando il funzionamento del comando ng serve che mostra un’anteprima dell’applicazione con la funzionalità di live reloading che può tornare molto utile in fase di sviluppo. Ripartiamo allora dalla nostra applicazione generata con Angular CLI e cerchiamo di capire come viene caricata e avviata un’applicazione Angular nel browser.

Riportiamo di seguito il contenuto della cartella my-angular-app/src che è la directory base della nostra applicazione predefinita.

tree src -a --dirsfirst -F
src
 app/
    app.component.css
    app.component.html
    app.component.spec.ts
    app.component.ts
    app.module.ts
 assets/
    .gitkeep
 environments/
    environment.prod.ts
    environment.ts
 browserslist
 favicon.ico
 index.html
 karma.conf.js
 main.ts
 polyfills.ts
 styles.css
 test.ts
 tsconfig.app.json
 tsconfig.spec.json
 tslint.json

3 directories, 19 files

Iniziamo a ispezionare il contenuto del file index.html, ovvero il file che viene servito dal server e visualizzato dall’utente nel browser.

<!-- file index.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My Angular App</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>

  <app-root></app-root>

</body>
</html>

Dal frammento di codice riportato sopra la prima cosa che salta subito all’occhio è l’assenza di qualsiasi elemento HTML di tipo <script>. Il motivo è che sarà Angular CLI a occuparsi di iniettare all’interno della pagina i file Javascript contenenti il codice che verrà eseguito. Notiamo quindi la presenza di un tag HTML particolare, ovvero <app-root>. Eseguiamo allora il comando ng serve –open che apre una finestra del nostro browser predefinito all’indirizzo http://localhost:4200/.

L’immagine sopra mostra del testo che, come abbiamo appena visto, non è esplicitamente presente nel file index.html analizzato. Prima di capire da dove viene il testo, usiamo una delle funzioni presente in tutti i browser installati su computer e visualizziamo il codice sorgente della pagina HTML che viene servita all’indirizzo http://localhost:4200/.

codice sorgente pagina index.html nel browser

Si tratta proprio della pagina index.html in cui Angular CLI ha inserito 5 elementi <script> che attraverso l’attributo src importano altrettanti file Javascript esterni.

Dalle due immagini riportate sopra è facile intuire che è AppComponent il componente a cui ‘appartiene’ il testo presente nella pagina index.html. Apriamo quindi il file app.component.ts che si trova al percorso my-angular-app/src/app.

/* file src/app/app.component.ts */
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = `Proprietà "title" del componente ${this.constructor.name}`;
}

Descriveremo in maggior dettaglio la sintassi presente nel frammento di codice riportato sopra a partire dalla prossima lezione. Fin da ora possiamo vedere che un componente viene definito attraverso una semplice classe a cui viene applicato il particolare decoratore @Component che riceve come argomento un oggetto aventi delle specifiche proprietà che caratterizzano il componente stesso. Ricordiamo dalle precedenti lezioni che in Angular un’applicazione viene suddivisa e strutturata in uno o più unità fondamentali e riutilizzabili che prendono il nome di componenti. Alla base di tale struttura esiste un componente radice che nell’applicazione generata da Angular CLI prende il nome predefinito AppComponent. Vedremo che ogni componente può avere altri componenti come discendenti. Tornando al file TypeScript app.component.ts, al suo interno viene definita la logica del componente AppComponent che ha una proprietà title. Per il componente AppComponent viene scelto un preciso selettore (app-root nel caso di AppComponent) che permette di inserire il componente all’interno del template di un altro componente o, come succede nel caso di AppComponent, direttamente nella pagina index.html. Per capire cosa verrà effettivamente mostrato sullo schermo, basterà aprire il file app.component.html che contiene il template relativo a questo componente.

<!-- app.component.html -->
<div style="text-align:center">
  <h1>
    {{ title }}
  </h1>
</div>

Si tratta di semplice codice HTML che definisce la struttura del componente AppComponent, ovvero cosa deve essere mostrato sullo schermo per il componente AppComponent. Infatti l’elemento <div> del frammento di codice riportato sopra verrà inserito come diretto discendente dell’elemento <app-root>.

codice HTML del componente AppComponent visto nel browser

Se consideriamo nuovamente il file app.component.html possiamo notare una particolare espressione, ovvero {{title}}. Si tratta dell’espressione di interpolazione che è utilizzata in Angular per inserire all’interno del template di un componente il valore di una proprietà definita nel file TypeScript contenente la logica del componente stesso.

A questo punto ci resta solo da vedere in che modo Angular avvia l’applicazione e a tal proposito analizziamo il codice presente nel file src/main.ts che è l’entry point della nostra applicazione.

/* file src/main.ts */
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

Come suggerito nella documentazione ufficiale, vengono prima importate le funzioni interne della libreria e in seguito abbiamo le istruzioni import relative ai moduli TypeScript dell’applicazione separati da una riga vuota. La funzione enableProdMode() viene invocata solo nell’ipotesi in cui abilitiamo la modalità production come acccade lanciando il comando ng build con l’opzione –prod.

La parte più importante del file main.ts è però la seguente:

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

Le due righe di codice riportate sopra costituiscono la strategia predefinita con cui Angular effettua l’avvio dell’applicazione facendosi carico di tutto il lavoro necessario per effettuare correttamente il rendering dell’applicazione all’interno del browser. Spulciando nel codice sorgente scopriamo che platformBrowserDynamic è una costante a cui viene assegnato un riferimento alla funzione createPlatformFactory() che restituisce un oggetto di tipo PlatformRef il quale, a sua volta, possiede un metodo bootstrapModule(). (bootstrapModule() restituisce una Promise per questo viene usata la funzione catch() per intercettare eventuali errori) Quest’ultimo si occupa di creare un istanza del modulo base AppModule che sarà il modulo di avvio della nostra applicazione.

Abbiamo visto che ogni applicazione può essere scomposta in più componenti che visivamente occupano una porzione dello schermo, mostrano delle informazioni e permettono all’utente di interagire con l’applicazione. Ma esistono anche altre entità (direttive, servizi, pipe ecc.), che analizzeremo in dettaglio nelle prossime lezioni, che insieme ai componenti possono essere mantenute e organizzate all’interno di un modulo Angular. Un modulo è quindi un meccanismo che serve per raggruppare componenti, direttive, servizi e pipe che hanno una qualche relazione fra loro. In questo senso i moduli permettono di strutturare e definire in maniera appropriata l’architettura di un’applicazione che può disporre di un numero indefinito di moduli, ma presenta un unico modulo base (root module) che nell’esempio visto in precedenza è AppModule.

Dal punto di vista del codice, un modulo viene definito attraverso una classe a cui viene applicato un particolare decoratore @NgModule.

/* file src/app/app.module.ts */
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Il decoratore @NgModule() riceve come argomento un oggetto di metadata con delle proprietà specifiche che vengono utilizzate per definire il modulo. In particolare notiamo la presenza di quattro array:

  • bootstrap contiene solitamente un unico componente che di norma prende il nome di AppComponent ed è il componente base creato durante la fase di avvio dell’applicazione;
  • providers presenta un insieme di servizi. Tratteremo l’argomento in una delle prossime lezioni e vedremo che dei servizi elencati nell’array providers del modulo AppModule ne viene creata una sola istanza disponibile in tutta l’applicazione;
  • declarations è la lista di componenti, direttive e pipe (ovvero entità ‘Declarables’) che fanno parte di un modulo. Ogni entità ‘Declarables’ deve appartenere a un solo modulo altrimenti il compilatore emette un errore. Le classi presenti nell’array declarations sono visibili all’interno del modulo, ma invisibili ai componenti degli altri moduli a meno che non vengano esplicitamente esportate dal modulo corrente che deve poi essere elencato nell’array imports del modulo che intende accedere a tali classi.
  • imports è l’insieme di moduli che intendiamo importare nel modulo corrente. Nel caso di AppModule importiamo il modulo BrowserModule.

Un’altra importante proprietà, non presente nel frammento di codice riportato sopra, è exports che elenca un insieme di componenti, direttive e pipe dichiarati in un modulo che possono essere utilizzati nel template di qualsiasi componente che fa parte di un secondo modulo che importa il primo.

Prima di concludere questa lezione è bene evidenziare la differenza fra i moduli Angular e TypeScript (o Javascript). Questi ultimi sono una funzionalità propria del linguaggio e indipendente da Angular utilizzati per organizzare il codice. Ogni file costituisce un modulo che consente di evitare l’uso di variabili globali e di nascondere i dettagli dell’implementazione di funzioni e classi avendo così pieno controllo su ciò che viene esposto e può essere usato in altri moduli. Al contrario i moduli in Angular sono classi a cui viene applicato il decoratore @NgModule e sono un concetto proprio del framework. Vengono utilizzati per strutturare delle applicazioni raggruppando entità come componenti, direttive, pipe ecc. che hanno una certa relazione fra loro. Ciascuna classe sarà definita in un proprio file e diventerà parte di un modulo quando viene elencata nel suo array declarations. I moduli importati attraverso l’array imports sono quindi diversi dai moduli TypeScript perché sono delle classi decorate con @NgModule piuttosto che normali moduli TypeScript. Se facciamo riferimento nuovamente al frammento di codice del modulo AppModule, a tale NgModule appartiene il solo componente AppComponent elencato nell’array ‘declarations’, ma per fare in modo che TypeScript capisca che cosa intendiamo per AppComponent importiamo la rispettiva classe definita all’interno del file app.component.ts.

Conclusioni

In questa terza lezione abbiamo illustrato, almeno superficialmente, in che modo Angular avvia un’applicazione nel browser. Nella prossima lezione inizieremo a lavorare con i componenti.

Pubblicitร