back to top

Angular: cos’è un componente e come crearne uno

Nelle precedenti lezioni abbiamo già accennato che un’applicazione Angular può essere vista come un insieme di componenti i quali occupano visivamente una porzione dell’intera applicazione, mostrano delle informazioni sullo schermo e permettono all’utente di interagire con l’applicazione stessa. Ciascun componente rappresenta un’entità configurabile e personalizzabile e può essere inserito in altri componenti. Un componente può quindi avere dei componenti discendenti a cui passare delle informazioni, viceversa questi ultimi possono notificare il componente genitore se qualche proprietà in essi contenuta ha subito delle modifiche.

Si verrà a creare così una struttura gerarchica di componenti con un elemento base (root component) che, quando creato con Angular CLI senza modificare alcun tipo di configurazione, prende il nome predefinito di AppComponent, presenta un selettore app-root ed è l’unico ad essere aggiunto all’applicazione inserendo l’elemento <app-root> direttamente nel file index.html. Non esiste una regola precisa che stabilisca quali componenti creare, ma è compito del programmatore capire come meglio strutturare un’applicazione.

Per esempio immaginiamo di voler realizzare un’applicazione per la lista della spesa come quella mostrata nell’immagine sottostante.

mockup esempio applicazione angular lista della spesa

Possiamo pensare di suddividere una simile applicazione come mostrato nella seguente immagine in cui abbiamo utilizzato un rettangolo di diverso colore per ciascun tipo di componente.

applicazione angular lista della spesa struttura componenti

Come già detto, si tratta di una possibile interpretazione per strutturare l’applicazione in diversi tipi di componenti. Ma non esiste in assoluto un modo univoco. A seconda dei dati, dei requisiti e delle funzionalità di un’applicazione potrebbe aver senso creare un certo componente o meno. Per esempio, facendo riferimento all’immagine in alto, si potrebbe pensare di creare un componente a parte per il pulsante ‘Aggiungi alla lista’ qualora volessimo implementare un particolare stile e definire delle animazioni particolari in caso di interazione dell’utente. Per tale motivo potremmo ipotizzare di definire un componente ‘Button’ configurabile in modo tale da assegnare di volta in volta un diverso testo, colore, dimensione ecc…

Considerando sempre l’immagine mostrata sopra, la nostra applicazione sarà quindi suddivisa in diversi componenti che costituiranno una struttura come quella riportata sotto.

applicazione angular lista della spesa albero componenti

Come definire un nuovo Componente senza Angular CLI

Abbiamo visto nella precedente lezione che dal punto di vista del codice, un componente è semplicemente una classe a cui è stato applicato il decoratore @Component. L’unico componente visto finora è AppComponent, creato direttamente da Angular CLI nel momento in cui abbiamo lanciato il comando ng new. Iniziamo a vedere come aggiungere un nuovo componente e almeno per ora creiamo tutti i file manualmente, successivamente ci avvarremo dell’ausilio di Angular CLI.

Partiamo quindi dall’applicazione creata con il comando ng new my-angular-app di cui riportiamo la struttura corrente della directory my-angular-app/src che è la cartella 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

All’interno della directory src/app/ creiamo quindi una sottocartella esempio-componente/ in cui aggiungeremo un file esempio-componente.component.ts. Infatti, per convenzione e per meglio strutturare un progetto, i file relativi a un componente sono inseriti all’interno di una directory identificata dal nome del componente stesso. Nel caso in cui tale nome fosse costituito da due o più parole, quest’ultime vengono separate da un trattino (pratica conosciuta anche come notazione kebab-case). Lo stesso vale per i file relativi al componente che presenteranno un nome del tipo nome-componente.component.estensione. Nel caso dei file che contengono la definizione dei test di unità, l’estensione è preceduta dalla parola spec che sta per Test specifications (stringhe utilizzate per identificare i test quando vengono eseguiti da un test runner).

Riportiamo dunque lo stato della cartella src/app dopo aver creato il nuovo file.

tree src/app  -a --dirsfirst -F
src/app
├── esempio-componente/
│   └── esempio-componente.component.ts
├── app.component.css
├── app.component.html
├── app.component.spec.ts
├── app.component.ts
└── app.module.ts

1 directory, 6 files

Definiamo poi il nuovo componente nel file esempio-componente.component.ts.

/* file esempio-componente.component.ts */
import { Component } from '@angular/core';

@Component({
  selector: 'app-esempio-componente',
  template: `<h2>Componente: {{ nomeComponente }}</h2>`,
  styles: [
    `h2 {
      color: midnightblue;
      font-family: 'Courier New', Courier, monospace;
    }`,
    `h2:hover {
      color: crimson;
    }`
  ]
})
export class EsempioComponenteComponent {
  nomeComponente = `${this.constructor.name}`;
}

Nel frammento di codice riportato sopra, importiamo dapprima il decoratore Component da @angular/core e definiamo una nuova classe con una sola proprietà nomeComponente che contiene il nome della classe il quale per convenzione termina con la stringa Component (per esempio ItemComponent) e viene scritto secondo la notazione PascalCase. (Il Pascal Case è una variante del CamelCase in cui, non solo nei nomi composti da due o più parole si uniscono quest’ultime tra loro senza lasciare spazi, ma la prima lettera è sempre maiuscola.)

Notiamo anche che grazie alla parola chiave export stiamo rendendo disponibile la classe EsempioComponenteComponent al di fuori del modulo TypeScript corrente.

Analizziamo ora il decoratore @Component al quale passiamo un oggetto con le seguenti tre proprietà:

  • selector indica in che modo verrà inserito il componente corrente all’interno del template di un altro componente. Consultando la documentazione ufficiale potrebbe sembrare che non sia possibile configurare nessuna opzione selector per il decoratore @Component, ma osservando con più attenzione notiamo subito che Component eredita tale proprietà da Directive (l’interfaccia Component estende l’interfaccia Directive). Parleremo delle direttive in una delle prossime lezioni e vedremo che esistono tre tipi di direttive. Una di queste è costituita dai Componenti i quali sono particolari direttive per cui viene definito un template. Per i componenti solitamente si utilizza come selettore una stringa il cui nome rispetta la notazione kebab-case. In questo modo, all’interno del template di un altro componente, andremo a inserire un elemento, associato a questo componente, avente come tag HTML <app-esempio-componente>. In questa occasione abbiamo anche anteposto il prefisso predefinito ‘app’ che avremmo potuto personalizzare. (Per altre opzioni disponibili per la proprietà selector, è possibile cosultare la documentazione) La configurazione predefinita di TSLint prevede infatti che se non è presente il prefisso nel selettore, viene mostrato un messaggio del tipo [tslint] The selector of the component "EsempioComponenteComponent" should have prefix "app".
  • La proprietà template è necessaria per definire un template e può essere sostituita da templateUrl. Abbiamo visto nelle precedenti lezioni che per il componente AppComponent abbiamo utilizzato una proprietà templateUrl a cui abbiamo assegnato il percorso relativo al file app.component.html che contiene il template del componente in formato HTML. In questo caso definiamo il template del componente all’interno di una stringa. Per comodità abbiamo utilizzato una delle funzionalità di ES2015, ovvero le stringhe template grazie alle quali possiamo distribuire il template su più righe. È facile intuire che per template lunghi e complessi è bene sostituire la proprietà template con templateUrl e utilizzare un file esterno. Il template del nostro componente sarà invece costituito da un solo elemento <h2> in cui abbiamo fatto uso della tecnica messa a disposizione da Angular dell’interpolazione che permette di inserire nel template il valore di una proprietà, nomeComponente nel caso specifico.
  • Come per le proprietà template e templateUrl, vale un ragionamento simile per styles e styleUrls che sono degli array di una o più stringhe rappresentanti, nel primo caso, dei gruppi di regole CSS, nel secondo, dei percorsi a fogli di stile da usare per il componente.

Abbiamo descritto le tre proprietà dell’oggetto che abbiamo passato come argomento al decoratore @Component, ma è bene soffermarci per un momento sulla proprietà styles. Nell’esempio appena visto abbiamo usato un array di due stringhe (stringhe template), ma avremmo potuto tranquillamente usare un array di una sola stringa contenente entrambe le regole come mostrato sotto.

/* file esempio-componente.component.ts modificato*/
import { Component } from '@angular/core';

@Component({
  selector: 'app-esempio-componente',
  template: `<h2>Componente: {{ nomeComponente }}</h2>`,
  styles: [
    `h2 {
      color: midnightblue;
      font-family: 'Courier New', Courier, monospace;
    }
    
    h2:hover {
      color: crimson;
    }`
  ]
})
export class EsempioComponenteComponent {
  nomeComponente = `${this.constructor.name}`;
}

L’uso delle stringhe template, al posto di normali stringhe racchiuse fra apici, apre a possibilità interessanti. Per esempio possiamo incorporare espressioni o variabili attraverso l’uso dell’interpolazione. (In questo caso facciamo riferimento a una funzionalità delle stringhe template indipendente da Angular)

/* file esempio-componente.component.ts modificato*/
import { Component } from '@angular/core';

/*  
*   definiamo una costante FONT_FAMILY
*   che usiamo come valore della proprietà font-family
*/
const FONT_FAMILY = ''Courier New', Courier, monospace;';

@Component({
  selector: 'app-esempio-componente',
  template: `<h2>Componente: {{ nomeComponente }}</h2>`,
  styles: [
    `h2 {
      color: midnightblue;
      font-family: ${FONT_FAMILY}
    }

    h2:hover {
      color: crimson;
    }`
  ]
})
export class EsempioComponenteComponent {
  nomeComponente = `${this.constructor.name}`;
}

È bene sottolineare inoltre che le regole CSS relative a un componente (sia quelle definite attraverso la proprietà styles sia quelle presenti nei file indicati da styleUrls), ovvero specificate nei metadati del decoratore @Component hanno validità solo all’interno del template del componente, sono applicate solo agli elementi presenti nel template e non sono ereditate da alcun componente discendente. Facendo sempre riferimento all’esempio, abbiamo definito due regole per degli elementi <h2>, ma Angular applicherà tali dichiarazioni solo agli elementi <h2> presenti nel template del componente EsempioComponenteComponent. Possiamo verificare tale comportamento negli strumenti per sviluppatori del browser in cui ci accorgiamo che Angular modifica i selettori delle regole da noi definite come mostrato nell’immagine sottostante. (In realtà non abbiamo ancora illustrato tutti i passaggi da completare prima di visualizzare una versione funzionante della nostra applicazione. Per ora limitiamoci ad analizzare l’immagine sottostante, spiegheremo a breve gli ultimi ritocchi da apportare prima di lanciare il comando ng serve)

dom applicazione angular

Come evidenziato nell’immagine, viene aggiunto un attributo univoco all’elemento <h2> presente nel template del componente EsempioComponenteComponent e di conseguenza viene utilizzato un selettore specifico, che fa uso di tale attributo, in modo da non entrare in collisione con i selettori usati in altre parti dell’applicazione.

Proseguendo col nostro esempio e riferendoci sempre all’immagine mostrata sopra, possiamo notare che il componente EsempioComponenteComponent è stato aggiunto come discendente del componente AppComponent. Per far ciò abbiamo modificato il template di quest’ultimo come mostrato sotto.

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

  <hr>

  <app-esempio-componente></app-esempio-componente>

</div>

Abbiamo inserito l’elemento <app-esempio-componente> all’interno del template del componente AppComponent. Anche per quest’ultimo abbiamo usato la tecnica dell’interpolazione per mostrare la sua proprietà nomeComponente.

/* 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 {
  nomeComponente = `${this.constructor.name}`;
}

A questo punto siamo pronti per visualizzare la nostra applicazione. Giusto? Non esattamente!

Infatti dobbiamo prima aggiungere il nuovo componente EsempioComponenteComponent al modulo AppModule. Ricordiamo infatti che ogni componente deve essere aggiunto a uno e un solo modulo. In caso contrario, avviando l’applicazione, verrebbe visualizzato nella console degli strumenti per sviluppatori del browser un messaggio come quello riportato sotto.

errore uncaught error template parse error

Modifichiamo allora il file app.module.ts come mostrato nel frammento di codice sottostante in cui abbiamo aggiunto il componente EsempioComponenteComponent nell’array declarations dell’oggetto passato come argomento al decoratore @NgModule.

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

import { AppComponent } from './app.component';
import { EsempioComponenteComponent } from './esempio-componente/esempio-componente.component';

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

Ricordiamo che affinché TypeScript sia in grado di capire a cosa ci si riferisce con EsempioComponenteComponent, dobbiamo importare la relativa classe presente nel file esempio-componente.component.ts ed esportata grazie alla parola chiave export.

Ricapitolando, abbiamo eseguito i seguenti passaggi per creare un nuovo componente:

  • Abbiamo creato un nuovo file esempio-componente.component.ts in cui abbiamo definito il nuovo componente EsempioComponenteComponent.
  • Abbiamo aggiunto un elemento <app-esempio-componente> nel template del componente base AppComponent. Il nome dell’elemento coincide con quello specificato nella proprietà selector dell’oggetto passato come argomento al decoratore @Component applicato al componente EsempioComponenteComponent.
  • Abbiamo aggiunto il nuovo componente nell’array declarations dell’unico modulo al momento presente nella nostra applicazione, ovvero AppModule.

Siamo quindi pronti per visualizzare la nostra applicazione nel browser grazie al server locale lanciato con il comando ng serve –open.

esempio componente angular

Per il debugging di un’applicazione Angular, possiamo installare l’estensione gratuita, disponibile per Chrome e Firefox, Augury che fornisce delle informazioni interessanti come mostrato nell’immagine sottostante.

augury debugging applicazione angular

Creare un nuovo componente con Angular CLI

A questo punto ci siamo resi conto che creare manualmente i file iniziali di un nuovo componente può essere dispendioso in termine di tempo, senza sottovalutare che è facile dimenticare qualche dettaglio o commettere degli errori. In nostro soccorso viene Angular CLI che semplifica il processo di creazione di un componente grazie al comando ng generate component <nome-componente> il quale accetta un gran numero di flag opzionali. Senza andare troppo in dettaglio vediamo in che modo avremmo potuto creare i file iniziali per il componente EsempioComponenteComponent attraverso AngularCLI. In questo caso usiamo però l’opzione –dry-run perché non vogliamo effettivamente creare i nuovi file e non vogliamo modificare lo stato corrente dell’applicazione.

ng generate component esempio-comp -s -t --no-spec --dry-run
CREATE src/app/esempio-comp/esempio-comp.component.ts (273 bytes)
UPDATE src/app/app.module.ts (631 bytes)

NOTE: The "dryRun" flag means no changes were made.

Il comando riportato sopra crea il file esempio-comp.component.ts per il nuovo componente e aggiunge quest’ultimo all’elenco declarations del modulo AppModule. Abbiamo usato un diverso nome (esempio-comp), perché, se provassimo a lanciare il medesimo comando indicando il nome del componente già esistente nella nostra applicazione, riceveremmo un messaggio di errore. Le opzioni che abbiamo usato sono le seguenti:

  • -s: indica la volontà di usare la proprietà styles invece di styleUrls al fine di inserire le regole CSS per il componente direttamente nel file esempio-comp.component.ts.
  • -t: come per l’opzione -s specifica che non useremo un file esterno per il template.
  • –no-spec: indica la volontà di non creare nessun file per i test d’unità.
  • –dry-run: chiede ad Angular CLI di non modificare la nostra applicazione, ma di limitarsi a stampare a video i nomi dei file che verrebbero creati in assenza di tale opzione.

Riepilogo

In questa quarta lezione abbiamo spiegato cos’è un componente in Angular e abbiamo visto come crearne uno sia manualmente che con l’ausilio di Angular CLI. Nella prossima lezione continueremo ad approfondire l’argomento in maggior dettaglio illustrando altre interessanti funzionalità che possono tornare utili nello sviluppo di un’applicazione.

Pubblicitร