back to top

I template in Angular: Interpolazione, Property binding, Event Binding e Two-way binding

Nelle precedenti lezioni abbiamo visto come definire un componente che, ricordiamo, è una semplice classe a cui viene applicato il decoratore @Component. Angular considera un componente come un caso particolare di direttiva (parleremo delle direttive nelle prossime lezioni), ovvero una direttiva avente un template. In effetti per definire completamente un componente utilizziamo una classe all’interno di un file TypeScript contenente le proprietà e i metodi propri del componente, un template per descrivere la struttura del componente e delle regole CSS per stabilirne l’aspetto. Concentriamo ora la nostra attenzione sui template che possono essere definiti direttamente all’interno dei file TypeScript attraverso la proprietà template dell’oggetto passato al decoratore @Component o in un file HTML esterno al quale faremo riferimento grazie alla proprietà templateUrl.

Il linguaggio HTML per i template dei componenti

I template consentono di definire la struttura di un componente stabilendo quali informazioni mostrare all’utente e permettendo a quest’ultimo di interagire con l’applicazione. Quando scriviamo i nostri template, usando il linguaggio HTML, possiamo utilizzare quasi tutti gli elementi HTML standard ad eccezione dell’elemento <script> che è vietato, al fine di eliminare il rischio di attacchi di iniezione di codice, e degli elementi <html> <body> e <base> che sarebbero di poca utilità. Angular permette di estendere il linguaggio HTML attraverso nuovi componenti e direttive da noi definiti. E non siamo limitati ad usare solo degli elementi statici, ma grazie a varie forme di data binding è possibile abilitare un certo grado di comunicazione fra la porzione del componente scritta in TypeScript e il template HTML attraverso un approccio dichiarativo.

L’interpolazione delle stringhe ({{…}}) in Angular

angular string interpolation

La prima forma di data binding che analizziamo è l’interpolazione di stringhe che è probabilmente la più semplice e l’abbiamo già incontrata in diverse occasioni negli esempi visti nelle precedeni lezioni. La sintassi è la seguente:

<p>Esempio interpolazione {{ espressioneTemplateValida }}</p>

Come mostrato nel frammento di codice riportato sopra, l’espressione di interpolazione prevede l’uso delle doppie parentesi graffe {{ espressioneTemplate }} che racchiudono un’espressione detta ‘Espressione Template’ (Template Expression) la quale viene elaborata da Angular e convertita in una stringa. Tale espressione non può essere disposta su più righe e, anche se solitamente è costituita soltanto dal nome di una proprietà definità nella classe del relativo componente, può contenere anche altri tipi di espressioni Javascript.

Consideriamo per esempio la seguente definizione del componente AppComponent:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-angular-app';
  getTitle(): string {
    return this.title;
  }
}

All’interno del file app.component.html potremo accedere alla proprietà title del componente AppComponent sia direttamente sia invocando il metodo pubblico getTitle().

/* file app.component.html */
<div style="text-align:center">
  <p>
    Proprietà AppComponent.title: {{ title }}!
  </p>
  <p>
    Metodo AppComponent.getTitle(): {{ getTitle().toUpperCase() }}!
  </p>
</div>

Il metodo getTitle() restituisce una stringa che convertiamo in caratteri maiuscoli attraverso il metodo String.prototype.toUpperCase() (Avremmo potuto usare lo stesso metodo anche direttamente sulla proprietà title). Ogni volta che la proprietà "title" subisce una variazione Angular provvede ad aggiornare automaticamente l’applicazione in modo da rispecchiare le modifiche apportate al componente. Otteniamo quindi il risultato mostrato nell’immagine sottostante.

esempio interpolazione stringhe in angular

Abbiamo detto che Angular elabora l’espressione presente fra doppie parentesi graffe e la converte in una stringa permettendo di utilizzare vari tipi di espressioni Javascript. Riportiamo allora alcuni esempi di espressioni consentite.

/* file app.component.html */
  <div style="text-align:center">
  <p>
    Operazioni matematiche come 2*3+4 = {{ 2 * 3 + 4 }}
  </p>
  <p>
    Operazioni che usano metodi o funzioni: 
    12 / AppComponent.sum(4, 2) = {{ 12 / sum(4,2) }}
  </p>
  <p>
    Espressione che usa l'operatore ternario: 
    {{ sum(1,2) === 3 ? 'ok' : 'errore' }}
  </p>
</div>

Sebbene sia possibile scrivere espressioni anche abbastanza complesse, è consigliato utilizzare delle espressioni semplici e facilmente leggibili che possano essere valutate da Angular in breve tempo al fine di non causare rallentamenti dell’applicazione. Non tutte le espressioni sono però consentite, non sono ammesse quelle che generano effetti collaterali come:

  • operatori di assegnamento (=, +=, …)
  • operatori di incremento e decremento (esempio num++, num–)
  • operatore new che crea una nuova istanza

Binding delle proprietà ([proprietà])

angular property binding

Esploriamo ora altri strumenti messi a disposizione da Angular per rendere i template dinamici e iniziamo a parlare del concetto di property binding che permette di assegnare a una proprietà di un elemento il valore di un’espressione valutata da Angular. Tale proprietà viene identificata col nome di target property. Un altro termine con cui ci si riferisce spesso al binding delle proprietà è one-way data binding evidenziando il fatto che le informazioni vengono inviate in modo unidirezionale dal componente, di cui si sta definendo il template, a una specifica proprietà di un elemento.

È bene inoltre evidenziare, come spiegheremo a breve in maggior dettaglio, che il binding di una proprietà ha a che fare esclusivamente con le proprietà di un elemento e non con i suoi attributi. Vediamo allora qual è la sintassi da usare e per farlo facciamo riferimento a un semplice esempio.

Creiamo un componente CarDescriptionComponent come mostrato nel seguente frammento di codice.

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

@Component({
  selector: 'app-car-description',
  template: `
    <h1 [textContent]="car.model"></h1>
    <img [src]="car.img" />
    <br>
    <button [disabled]="!car.available">Compra</button>
  `
})
export class CarDescriptionComponent {
  car = {
    model: 'Alfa Romeo Giulia',
    img: 'assets/images/alfa-giulia.jpg',
    available: false
  };
  constructor() {
    setTimeout(() => {
      this.car.model = 'Nuova ' + this.car.model;
      this.car.img = 'assets/images/nuova-alfa-giulia.jpg';
      this.car.available = true;
    }, 3000);
  }
}

Dal codice presente nella proprietà template dell’oggetto passato come argomento al decoratore @Component applicato alla classe CarDescriptionComponent, deduciamo che, attraverso la sintassi [target-property]="expression", facciamo uso del binding delle proprietà (L’uso delle parentesi quadre è la forma abbreviata e preferita, in alternativa è possibile usare la forma estesa che prevede l’utilizzo del prefisso bind- da anteporre alla proprietà target. Per esempio <img bind-src="car.img" />). In questo modo associamo la proprietà textContent dell’elemento HTMLHeadingElement H2 al valore della proprietà car.model del nostro componente. Lo stesso avviene per la proprietà src dell’elemento immagine. Grazie alla proprietà disabled dell’HTMLButtonElement decidiamo di disabilitare o meno il pulsante in base al valore assunto dalla proprietà car.available del componente CarDescriptionComponent. È bene mettere in evidenza che l’espressione template valutata da Angular restituisce in tutti i casi un valore del tipo atteso dalla proprietà target. Nei primi due casi si tratta di semplici stringhe, nel caso della proprietà disabled dell’elemento Button il valore assegnato è di tipo Boolean. In generale l’espressione template elaborata da Angular deve essere sempre del tipo atteso dalla proprietà target. Angular elabora quindi le diverse espressioni, compila il template e genera il codice HTML per il componente.

dettagli esempio property binding

Come possiamo notare all’interno della pagina viene inserita inizialmente l’intestazione, un’immagine e un pulsante che però è disabilitato. Ma nel costruttore del nostro componente abbiamo anche invocato la funzione setTimeout che dopo 3 secondi modifica i valori delle tre proprietà dell’oggetto car. Tutto ciò innesca un meccanismo interno ad Angular (Change Detection) che porta ad aggiornare automaticamente le informazioni presenti nella nostra applicazione come mostrato nel breve video riportato sotto.

Anche nel caso del one-way property binding possiamo fare uso di espressioni Javascript più complesse come mostrato nel frammento di codice sottostante.

<button [disabled]="disable()">Compra</button>
<button [disabled]="0 < 1 ? true : false" >Compra</button>

Property binding: attributi HTML vs proprietà del DOM

Prima di proseguire a parlare di un altri tipi di data binding presenti in Angular, è bene soffermarci un attimo a fare delle precisazioni importanti.

Abbiamo già accennato in precedenza che quando parliamo di property binding facciamo sempre riferimento alle proprietà degli elementi del DOM e non agli attributi definiti dal codice HTML. Chiariamo meglio che cosa intendiamo.

Quando apriamo una pagina HTML, il codice viene analizzato dal browser e viene costruito il DOM (Document Object Model) il quale tratta un documento come un albero in cui ogni nodo è un oggetto che rappresenta una parte del documento stesso. Il DOM è quindi una rappresentazione orientata agli oggetti di una pagina web a cui possiamo apportare delle modifiche attraverso un linguaggio come Javascript.

Gli attributi rappresentano un costrutto sintattico del codice HTML, ricevono un valore sempre di tipo stringa e provvedono a inizializzare delle proprietà dell’elemento del DOM relativo all’elemento HTML su cui è stato applicato l’attributo.

Una proprietà invece è definita dal DOM ed è propria dell’oggetto del DOM costruito per rappresentare un certo elemento HTML. Le proprietà possono assumere valori di diverso tipo (stringa, boolean, numerico).

Gli attributi HTML e le proprietà del DOM sono quindi due concetti diversi e distinti anche se a volte il nome di un attributo HTML coincide con quello di una proprietà.

Infatti solo alcuni attributi HTML hanno un’omonima proprietà DOM. La proprietà src dell’elemento immagine vista sopra è un esempio. Esistono però attributi che non hanno una proprietà corrispondente come il caso dell’attributo colspan. Viceversa vi sono proprietà che non hanno degli attributi corrispondenti. (La proprietà textContent, usata nel frammento di codice precedente, è un esempio di quest’ultimo caso)

All’interno di un template, quando racchiudiamo una proprietà fra parentesi graffe, chiediamo quindi ad Angular di valutare l’espressione presente fra virgolette ("espressione") e assegnare il valore ottenuto alla proprietà dell’elemento del DOM corrispondente a un certo elemento HTML o, come inizieremo a vedere dalla prossima lezione, a proprietà di componenti e direttive.

Facendo riferimento all’esempio visto in precedenza e limitandoci al solo elemento <button>, abbiamo racchiuso la proprietà disabled fra parentesi quadre per associarla al valore della proprietà car.available del componente CarDescriptionComponent che controlla quindi se il pulsante deve essere disabilitato o meno. La proprietà disabled ha infatti un valore predefinito pari a false per gli HTMLButtonElement. Se rimuovessimo le parentesi quadre, assegneremmo all’attributo disabled la stringa "!this.car.available" che avrebbe poco senso visto che l’attributo disabled si comporta in modo particolare e il suo valore è irrilevante, a maggior ragione in questo caso specifico in cui gli verrebbe assegnata una stringa qualsiasi. Se è presente l’attributo disabled, il pulsante viene disabilitato. Se viene rimosso, il pulsante viene nuovamente abilitato.

esempio errato attributo disabled applicato a un pulsante in un'applicazione Angular

L’immagine riportata sopra conferma quanto appena detto e il risultato è che il valore dell’attributo è ignorato e il pulsante è comunque disabilitato.

Interpolazione vs Binding delle proprietà

A questo punto abbiamo capito che il one-way binding assegna alla proprietà di un elemento del DOM, associato a un certo elemento HTML, il valore dell’espressione valutata da Angular. Tale valore avrà un tipo specifico che può essere una stringa, un numero, ecc.. Nel caso in cui si tratti di una stringa, possiamo tranquillamente scegliere di usare la tecnica dell’interpolazione al posto del binding di una proprietà. Non esiste una ragione specifica per preferire una all’altra, ma è compito del team di sviluppatori, che lavorano a una certa applicazione, scegliere. L’importante è mantenere uno stile consistente e prevedibile. In base a quanto abbiamo appena affermato, gli esempi sottostanti sono tutti validi.

/* file app.component.html */

<!-- Queste due righe di codice hanno lo stesso risultato finale -->
<h1 [textContent]="car.model"></h1>
<h1>{{ car.model }}</h1>

<!-- Queste altre due righe di codice hanno lo stesso risultato finale -->
<img [src]="car.img" />
<img src="{{ car.img }}" />

Binding di attributi, classi e stile

Abbiamo visto finora qual è la sintassi da usare per il binding delle proprietà. Angular offre però una sintassi simile anche per i casi più rari di binding di attributi, classi e stile. Iniziamo ad analizzare il primo caso.

Binding di attributi

Abbiamo detto in precedenza che esistono dei casi in cui un attributo di un elemento HTML non ha un’equivalente proprietà dell’elemento del DOM ad esso associato. In questi casi è possibile utilizzare il binding degli attributi. La sintassi è simile a quella del binding delle proprietà solo che invece di una proprietà racchiuderemo fra parentesi quadre il nome di un attributo preceduto dal prefisso attr seguito da un punto. Assegneremo quindi un’espressione che, una volta elaborata da Angular, si risolve in una stringa.

È consigliabile usare il binding di attributi solo nel caso non sia possibile assegnare un valore tramite proprietà come nel caso dell’attributo colspan.

<table border="2">
  <tr>
    <th [attr.colspan]="colspan">Nome Completo</th>
  </tr>

  <tr>
    <td>Mario</td>
    <td>Rossi</td>
  </tr>
</table>

Binding dell’attributo class di un elemento

Il binding dell’attributo class può essere usato per generare in modo dinamico il nome di una o più classi associate ad un elemento, anche se, in quest’ultimo caso, è consigliabile utilizzare la direttiva NgClass di cui parleremo in una delle prossime lezioni.

Vediamo quindi come aggiungere o rimuovere dei nomi di classi ad un elemento attraverso il bind di classe. La sintassi ricorda ancora una volta quella delle proprietà solo che in questo caso racchiuderemo fra parentesi quadre il termine class seguito opzionalmente da un punto (.) e dal nome di una classe specifica.

<div [class]="theme"></div>

Facendo riferimento all’esempio riportato sopra, dovremmo definire all’interno del componente una proprietà theme. Se per esempio questa avesse valore "light", il codice HTML generato sarebbe il seguente:

<div class="light"></div>

Se poi variamo il valore della proprietà theme, viene automaticamente aggiornato anche il valore dell’attributo class.

Il binding dell’attributo class sostituisce completamente eventuali nomi specificati in maniera statica.

<div class="light main content" [class]="theme"></div>

Nell’esempio mostrato sopra, l’attributo class dell’elemento <div> sarà pari al valore della proprietà theme. Se quest’ultima è pari a "dark", il codice HTML generato sarà il seguente.

<div class="dark" ></div>

È infine possibile assegnare a un elemento una classe specifica a seconda del valore dell’espressione template valutata da Angular. Se questa restituisce un valore equiparabile al valore booleano true (truthy value), viene aggiunto il nome della classe che viene però rimosso in caso contrario.

<!-- Supponendo di avere il seguente template -->
<div [class.active]="isActive()"></div>
<p [class.hide]="!isAvailable"></p>
<p class="light main content" 
  [class.hide]="!isAvailable" 
  [class.light]="!lightTheme"></p>

<!-- Se isAvailable === false lightTheme === true -->
<!-- e isActive() restituisce true, -->
<!-- verrà generato il seguente codice-->

<div class="active"></div>
<p class="hide"></p>
<p class="light hide main content"></p>

Binding della proprietà style

A questo punto dovrebbe risultare piuttosto semplice capire il funzionamento del prossimo tipo di binding che andiamo a descrivere.

<div [style.color]="getTextColor()"></div>

Nel frammento di codice riportato sopra associamo alla proprietà CSS color il valore restituito dal metodo getTextColor() del componente. Se per esempio quest’ultimo restituisce la stringa ‘#FF0011‘, nel browser visualizzeremo un elemento come il seguente.

<div style="color: rgb(255, 0, 17);"></div>

Per alcune proprietà CSS è possibile specificare anche direttamente l’unità espressa in px, em, % o rem.

<div [style.width.px]="width"></div>
<p [style.background-color]="correct ? 'forestgreen' : 'tomato'"></p>

Nella prossima lezione vedremo come usare il binding unidirezionale delle proprietà per componenti e direttive da noi definite.

Gestire gli eventi del DOM

Angular event binding

La tecnica dell’interpolazione e del binding di una proprietà sono sostanzialmente dei modi per trasferire delle informazioni dal componente agli elementi presenti all’interno del suo template. Grazie al meccanismo, detto event binding, Angular consente invece di intercettare gli eventi generati dall’interazione dell’utente con l’applicazione e di rispondere in maniera appropriata. Nei prossimi articoli mostreremo come creare degli eventi personalizzati, nel frattempo vediamo qual è la sintassi da usare per gestire qualsiasi tipo di evento del DOM.

<button (click)="pay()">Compra</button>

Anche in questo caso la sintassi è piuttosto semplice e prevede l’uso di parentesi tonde, in cui racchiudiamo il nome di un evento (target event), seguite dal segno uguale e dalle virgolette nelle quali esprimiamo l’operazione da eseguire ogni volta che si verifica quell’evento. Esiste anche una sintassi alternativa che consiste nel rimuovere le parentesi tonde e anteporre il prefisso on- al nome dell’evento, ma la prima è quella più diffusa.

<button on-click="onClick()">Compra</button>

Vediamo allora un semplice esempio in cui creiamo un componente che presenta un pulsante e un contatore. Ogni volta che clicchiamo sul pulsante viene incrementato il contatore e automaticamente Angular aggiorna l’applicazione.

import { Component } from '@angular/core';

@Component({
  selector: 'simple-counter',
  template: `
    <button (click)="onClick()">+1</button>
    <span>Count: {{ count }}</span>
  `,
  styleUrls: ['./counter.component.css']
})
export class CounterComponent {
  count = 0;
  onClick() {
    this.count++;
  }
}

In alternativa possiamo aggiornare il contatore direttamente senza invocare il metodo onClick(), per far ciò basta modificare l’azione da eseguire al verificarsi dell’evento ‘click’ e inserire un’istruzione come quella riportata nel frammento di codice sottostante.

import { Component } from '@angular/core';

@Component({
  selector: 'simple-counter',
  template: `
    <!-- aggiorna direttamente la proprietà count -->
    <button (click)="count = count + 1">+1</button>
    <span>Count: {{ count }}</span>
  `,
  styleUrls: ['./counter.component.css']
})
export class CounterComponent {
  count = 0;
}

In entrambi i casi otterremo un risultato simile a quello mostato nella seguente immagine.

esempio contatore angular

Nell’esempio appena illustrato abbiamo però trascurato un dettaglio. Per ogni evento possiamo infatti accedere ad un particolare oggetto, ovvero $event che contiene le informazioni relative all’evento che si è verificato, così come avviene normalmente in Javascript. Potremo quindi accedere a eventuali informazioni utili o invocare metodi come Event.stopPropagation() che blocca la propagazione dell’evento o Event.preventDefault() che, se l’evento lo consente, cancella l’azione predefinita associata a un evento.

Vediamo quindi un esempio in cui utilizziamo l’oggetto $event. Intercettiamo l’evento mouseover per un elemento <div> e mostriamo i valori delle coordinate X e Y della posizione corrente del cursore del mouse.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <div class="box" (mousemove)="onMouseMove($event)"></div>
      <span>X: {{ coord.x }}</span>
      <span>Y: {{ coord.y }}</span>
    </div>
  `,
  styles: [
    `
    .container{
      display: grid;
      width: 300px;
      grid-template-columns: 1fr 1fr;
      grid-template-rows: auto;
    }
    .box {
      grid-column: 1 / -1;
      width: 300px;
      height: 300px;
      border: 2px solid black;
    }
    span {
      font-size: 2em;
      grid-row: 2 / -1;
    }
    `
  ]
})
export class AppComponent {
  coord = {
    x: 0,
    y: 0
  };
  onMouseMove(event) {
    this.coord.x = event.offsetX;
    this.coord.y = event.offsetY;
  }
}
esempio angular event binding oggetto $event

Two-way data binding in Angular

angular two way data binding

Per concludere questa lezione parliamo del l’ultimo caso di data binding che per certi versi è anche il più interessante, ovvero il two-way data binding. Questa tecnica permette di mantenere sincronizzate le informazioni presenti nel template con le proprietà del componente ad esse associate. Ogni volta che quest’ultime subiscono una variazione, viene aggiornata l’applicazione in modo da riflettere le ultime modifiche. Al contrario se le informazioni presenti nel template cambiano, alle proprietà corrispondenti del componente viene assegnato il nuovo valore.

Possiamo implementare un mecccanismo simile manualmente come combinazione di event binding e property binding come mostrato nell’esempio sottostante nel caso di un elemento <input>.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Convertitore Euro/Dollaro</h1>
    <label for="amount">Somma in Euro da convertire</label>
    <div>
      <input
        id="amount"
        size="20"
        [value]='eur'
        (input)="onInput($event)">
      <span style="margin-left: 10px;">
        {{ eur | currency:'EUR' }} = {{ usd | currency:'USD' }}
      </span>
    </div>
  `,
})
export class AppComponent {
  eur = 0;
  usd = 0;
  conversionRate = 1.1498;
  onInput(event) {
    this.eur = event.target.value;
    this.usd = this.eur * this.conversionRate;
  }
}

Nel frammento di codice riportato sopra non abbiamo fatto altro che utilizzare le tecniche appena viste. Ogni volta che inseriamo una cifra nel campo <input> viene invocato il metodo onInput() a cui viene passato l’oggetto $event dal quale estraiamo il valore corrente per calcolare il nuovo valore in dollari. All’interno del template abbiamo associato alla proprietà value dell’HTMLInputElement il valore della proprietà eur del componente. È proprio quest’ultima che determina di volta in volta il valore da mostrare nell’elemento <input>. Abbiamo inoltre intercettato l’evento input al verificarsi del quale invochiamo il metodo del componente. La sintassi usata nel template non presenta nulla di nuovo a parte l’uso della Pipe currency (eur | currency:'EUR' e usd | currency:'USD'), funzionalità di Angular che prende un valore in ingresso e lo trasforma in una stringa che presenta il simbolo della valuta. Per il momento trascuriamo le pipe in quanto torneremo sull’argomento in una delle prossime lezioni.

L’operazione, che prevede di visualizzare delle informazioni contenute in una certa proprietà di un componente e di aggiornare quest’ultima quando un utente apporta dei cambiamenti ai valori presenti nel template, è piuttosto frequente. Per questo motivo Angular offre una direttiva particolare che rende il meccanismo del two-way data binding, quando si lavora con elementi di input, estremamente semplice. Il nome di tale direttiva è ngModel (da non confondere con il decoratore @NgModule) che fa parte del modulo FormsModule.

Modifichiamo quindi l’esempio appena visto, facendo uso della direttiva ngModel e ottenendo il medesimo risultato finale.

Per prima cosa dobbiamo ricordare di aggiungere il modulo FormsModule nell’elenco dei moduli importati di AppModule.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule // necessario per ngModel
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

A questo punto possiamo modificare il nostro esempio come mostrato nel frammento di codice riportato sotto.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Convertitore Euro/Dollaro</h1>
    <label for="amount">Somma in Euro da convertire</label>
    <div>
      <input
        id="amount"
        size="20"
        [ngModel]='eur'
        (ngModelChange)="update($event)">
      <span style="margin-left: 10px;">
        {{ eur | currency:'EUR' }} = {{ usd | currency:'USD' }}
      </span>
    </div>
    <button (click)="update(eur + 10)">Aggiungi 10 EUR</button>
  `,
})
export class AppComponent {
  eur = 0;
  usd = 0;
  conversionRate = 1.1498;
  update(value) {
    this.eur = +value || 0;
    this.usd = this.eur * this.conversionRate;
  }
}

Come possiamo notare dal frammento di codice riportato sopra abbiamo usato la direttiva ngModel per implementare il meccanismo del two-way data binding. In questo caso al metodo update() abbiamo sempre passato come argomento $event, ma in questo caso, visto che stiamo facendo uso di ngModel, si tratta del valore corrente del campo <input> e non di un oggetto di tipo Event. Con il codice mostrato sopra, il valore dell’elemento <input> rimane sincronizzato con la proprietà eur del componente AppComponent. Ogni volta che uno dei due viene modificato, l’altro viene aggiornato.

Possiamo semplificare ulteriormente la sintassi della direttiva ngModel combinando le parentesi quadre del one-way binding [property] e le parentesi tonde del bindig di eventi (event). Otteniamo dunque la sintassi semplificata [(ngModel)] che è equivalente alla sintassi estesa vista nell’esempio precedente.

<input [(ngModel)]='property'>

Ricordiamo che, in questo modo, ogni volta che l’utente modifica il campo <input>, viene aggiornata la proprietà property del componente. Viceversa se a quest’ultima viene assegnato un nuovo valore, Angular provvede a cambiare il contenuto del campo <input> aggiornando la proprietà value dell’HTMLInputElement associato.

Se ora modifichiamo l’esempio appena visto e utilizziamo la forma semplificata di [(ngModel)], ci dobbiamo però domandare come fare ad aggiornare la proprietà usd del componente AppComponent. Infatti la sintassi abbreviata di ngModel permette di mantenere sincronizzati i valori della proprietà eur del componente AppComponent e della proprietà value dell’elemento del DOM associato all’elemento <input>. Ma come fare per aggiornare la proprietà usd ogni volta che cambia eur? Cerchiamo di rispondere a questa domanda e vediamo un possibile modo per eseguire l’operazione di aggiornamento della proprietà usd di AppComponent ogni volta che viene modificata la proprietà eur e continuare ad utilizzare allo stesso tempo la sintassi semplificata di ngModel.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Convertitore EUR/USD</h1>
    <label for="amount">Somma in Euro da convertire</label>
    <div>
      <input
        type="number"
        id="amount"
        size="20"
        [(ngModel)]='eur'
        >
      <span>
        {{ (eur ? eur : 0) | currency:'EUR' }} = {{ usd | currency:'USD' }}
      </span>
    </div>
    <button (click)="updateEUR(10)">+10 EUR</button>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private _eur = 0;
  usd = 0;
  conversionRate = 1.1498;

  get eur(): number {
    return this._eur;
  }

  set eur(value: number) {
    this._eur = value;
    this.usd = this._eur * this.conversionRate;
  }

  updateEUR(increment: number) {
    this.eur += increment;
  }
}

Abbiamo riscritto e migliorato l’esempio visto in precedenza risolvendo contemporaneamente alcuni dei problemi presenti nelle precedenti versioni e utilizzando la sintassi abbreviata di [(ngModel)] a cui abbiamo associato la proprietà eur di AppComponent. Di quest’ultima abbiamo definito i metodi getter/setter in modo da aggiornare la proprietà usd ogni volta che eur subisce una variazione. Abbiamo inoltre aggiunto una proprietà privata _eur in cui conserviamo il valore effettivo in Euro. Ogni volta che digitiamo un nuovo valore all’interno del campo <input>, viene invocato il setter della proprietà eur. Lo stesso avviene quando, nel metodo updateEUR(), aggiorniamo la medesima proprietà incrementando il suo valore di una quantità pari a increment. Tramite il getter della proprietà eur accediamo invece al valore conservato nella proprietà privata _eur e lo mostriamo all’interno del template.

Riepilogo

In questa lezione abbiamo iniziato ad illustrare alcuni degli strumenti messi a disposizione da Angular per lavorare con i template. Abbiamo inizialmente visto come usare la tecnica dell’interpolazione e del binding delle proprietà per passare delle informazioni agli elementi presenti all’interno di un template. Abbiamo quindi mostrato come intercettare gli eventi che si verificano ogni volta che un utente interagisce con la nostra applicazione. E infine abbiamo parlato del meccanismo del two-way data binding che consente di mantenere sincronizzate le proprietà di un componente e le informazioni presenti nel suo template. Nella prossima lezione continueremo a lavorare con i template e vedremo come è possibile annidare dei componenti e passare delle informazioni dal componente genitore ai componenti figli e viceversa.

Pubblicitร