angular service e form

in Javascript, Programmazione

Angular Service & C.

Reading Time: 6 minutes

Prima di procedere con lo sviluppo dell’applicazione ASPNet Core ed Angular, alcuni concetti che verranno utilizzati. Come tutte le applicazioni web che si rispettino, l’applicazione Angular necessita di interagire con l’utente: il metodo tradizionale per effettuare operazioni CRUD è l’utilizzo di Form. Angular mette a disposizione strumenti molto flessibili per la gestione dei dati, e le scelte delle strategie da utilizzare dovranno essere fatte in base al tipo di complessità richiesta dalla form/applicazione stessa.

Angular Form

Angular mette a disposizone due meccanismi per la gestione delle form:

  • Template Driven: la logica dei dati viene gestita per la maggior parte all’interno del template della pagina, come avviene per le applicazioni html tradizionali
  • Reactive Form: la logica è gestita direttamente come proprietà della classe associata alla form stessa

Per poter utilizzare Template Driven è necessario importare nel file app.module.ts il modulo FormsModule:

import {BrowserModule} from '@angular/platform-browser'

in modo da poterlo utilizzare in tutti i componenti della nostra applicazione.

Se il progetto è stato gestito tramite CLI (e solitamente è cosi!) l’operazione precedente potrebbe non essere necessiaria, ma è comunque necessario un controllo. E’ necessario utilizzare il condizionale perchè in alcune versioni della CLI non viene effettuata l’aggiunta automatica del riferimento.

Per poter utilizzare le Reactive Form è necessario importare il modulo ReactiveForm:

import {ReactiveFormModule} from '@angular/forms'

Form Model

In Angular nella definizione delle form non è più necessario utilizzare gli attributi action e method perchè le action sono gestite direttamente all’interno del componente (o meglio dalla sua classe). Lo stesso principio è applicato anche per recuperare/settare l’attributo value di ogni singolo elemento. Si può riassumere il funzionamento utilizzando il concetto di Form Model, composto da tre blocchi:

  • FormControl
  • FormGroup
  • FormArray

che tramite alcune direttive specifiche possono essere associati agli elementi del DOM. Supponiamo di avere un elemento di tipo input:

<input type="text" name="nome" />

il primo passo per creare il binding con la relativa proprietà del model è quella di inserire la keyword ngModel:

<input type="text" name="nome" ngModel />

o in alternativa:

<input type="text" name="nome" [ngModel] />

Nel caso precedente abbiamo creato un binding di tipo unidirezionale: dal template al componente. Dalla classe del componente è quindi possibile effettuare la lettura del valore presente sulla form ma non settarlo. Per rendere la proprietà leggibile e settabile è necessario modificare la sintassi precedente nel seguente modo:

<input type="text" name="nomecampo" [(ngModel)]="nomecampo_modello" />

In alcune versioni precedenti di angular, veniva spesso utilizzato questo tipo di rappresentazione:

<input type="text" name="nomecampo" (input)="nomecampo=$event.target.value" />

che non dovrebbe stupire più di tanto: al controllo di tipo input viene associato un evento di tipo input collegato ad una proprietà della classe del component chiamata nomecampo.

Passaggio dei dati dall’HTML al component

L’ultimo passo riguarda l’impostazione del meccanismo che permette di inviare i dati della form: in precedenza abbiamo specificato che non è necessario definire il method e la action nell’HTML.

Per poter interagire con il valore deve essere utilizzato il tag <form> aggiungendo:

  • un riferimento ad una variabile locale, definita con la notazione #
  • l’utilizzo di ngForm per indicare che la form dovrà essere gestita dal framework di angular
  • l’utilizzo di ngSubmit per effettuare il “submit” tramite angular

Un esempio di form che utilizza le direttive angular:

<form #mioform="ngForm" (ngSubmit)="invio(mioform)">
    <label for="nome">Nome</label>
    <input type="text" name ="nome" placeholder="Name" [(ngModel)]="nome_modello"/>
    <label for="email">Email</label>
    <input type="text" name="email" placeholder="Email" [(ngModel)]="email_modello">
    <button type="submit">Send</button>
</form>

si può notare la definizione della form tramite #mioform=”ngForm” che indica ad angular la presenza di una form. Il submit viene gestito utilizzando (ngSubmit)=”invio(form”) che consente di passare alla funzione invio(…) l’intera collection della form. Ogni singolo elemento della form è bindato sulla classe interna mediante [(ngModel)]: come descritto in precedenza utilizzando questo tipo di sintassi, stiamo eseguendo il binding in modalità bidirezionale (la classe del componente può effettuare può scrivere il singolo valore direttamente verso la form). Se avessimo utilizzato la sintassi [ngModel] non sarebbe possibile effettuare il setting del valore verso il template.

Non resta che scrivere una semplice funzione invio(…) eseguita al momento della submit della form:

invio(form: NgForm) {
    console.log(form.controls['nome'].value);
    console.log(form.controls['email'].value);
  }

Il codice è molto semplice: la funzione accetta in ingresso un oggetto di tipo NgForm, nel nostro caso chiamato form) ed è possibile effettuare il getter/setter dei valori utilizzando il seguente codice:

form.controls['nome'].value

A volte può essere utile recupeare tutti gli elementi presenti nella form e ad esempio convertirlo in Json:

JSON.stringify(form.value)

Inserendo il codice precedente all’interno della funziona invio(…) potremo generare il JSON direttamente dai campi provenienti dalla form. Esistono anche altri meccanismi per effettuare il binding dell’intera form verso la classe del component, ma per l’esempio che vogliamo realizzare questo è più che sufficiente.

Validazione dei valori inseriti

Uno dei requisiti che deve avere una form è sicuramente quello di poter validare i dati inseriti dall’utente. Nello sviluppo di applicazioni web ci si concentra su due tipo di validazione: la validazione lato client e la validazione lato server. Ogni applicazione ben scritta dovrebbe implementare entrambe queste validazioni.

La validazione lato server viene implementata nel backend dell’applicazione, nel nostro caso in ASPNet Core 3. E’ comunque necessario definire una primo controllo dei dati inseriti dell’utente, prima di effettuare la loro gestione: Angular mette a disposizione alcune direttive anche per questo.

Vediamo, ad esempio, come è possibile definire i campi della form come richiesti, ed impedire il submit fino a quando il loro valore non è valorizzato.

Modifichiamo quindi la form precedente aggiungendo required nelle input HTML che vogliamo rendere obbligatori:

<form #mioform="ngForm" (ngSubmit)="invio(mioform)">
    <label for="nome">Nome</label>
    <input type="text" name ="nome" placeholder="Name" [(ngModel)]="nome_modello" required/>
    <label for="email">Email</label>
    <input type="text" name="email" placeholder="Email" [(ngModel)]="email_modello" required>
    <button type="submit">Send</button>
</form>

Ovviamente questa modifica non è sufficiente per disabilitare il pulsante submit prima della validazione. Come in ASPNet Core, anche in Angular è possibile verificare se i dati di una form sono validi utilizzando un’apposita funzione proprietà associati ad ogni form:

form.valid 

viene valorizzata con true/false in base ai dati presenti nella form stessa.

Si può, quindi, modificare il codice del pulsante submit in questo modo:

<button type="submit" [disabled]="!mioform.form.valid">Send</button>

in modo da renderlo cliccabile solo quando lo proprietà valid è true, e quindi entrambi i campi required sono valorizzati.

Tips per il passaggio dei valori

Spesso all’interno del template si ha la necessità di recupeare un singolo valore presente all’interno di una singola casella di testo. In questo caso, possiamo utilizzare una notazione più compatta:

<input type="text" #msg required />
<button (click)="invio(msg.value)"; msg.value=''")>Send</button>

Il codice precedente imposta una valore locale al template chiamata msg (si noti la presenza del carattere # ad indicare che si tratta di una variabile locale. Al pulsante è stato aggiunto un evento click che eseguirà due operazioni: non dovrebbe stupire la presenza di due istruzioni che verranno eseguite una di seguito all’altra ed associate allo stesso evento.

Il valore passato alla funzione invio(…) è proprio il value della casella di testo.

Passaggio dei valori dalla classe alla template

Ci troviamo nel caso di dover valorizzare i valori del template a partire dalla classe del component. Questa operazione è necessaria, ad esempio, quando si vuole valorizzare i dati presenti nella form con i dati provenienti da servizi esterni (API). Ovviamente, dovrà essere il component ad effettuare il binding.

La sintassi che dovrà essere utilizzata è :

nomecampo_modello  = "valore"

dove nomecampo_modello è il valore che è stato definito nel template tramite [(ngModel)].

Angular Service

Nello sviluppo delle funzionalità di un componente è consigliato delegare a servizi esterni l’implementazione di particolari logiche, come ad esempio il recupero di dati JSON provenienti da API. In Angular, questa suddivisione prende il nome di Service che vengono iniettati all’interno dei componenti attraverso la DI. La creazione di servizi separati dai componenti consente anche di migliorare la fase di test dell’applicazione: basterà modificare la configurazione a livello centrale per iniettarla in tutta l’applicazione.

Un service è una particolare classe che è stata decorata tramite il decoratore @Injectable. Per il resto un service può avere tutto quello che può avere un component ad esempio, un costruttore, delle proprietà e dei metodi. E’ una classe a tutti gli effetti.

Un service può essere creato tramite la CLI, utilizzando il comando:

ng g service nome_servizio

Solitamente, per organizzare al meglio la struttura dell’app, viene creata una cartella service all’interno della quale vengono posizionati si singoli file .ts

Una volta dichiarato, un service, deve essere registrato a livello di applicazione. A partire dalla versione 6.0 di Angular, è sufficiente inserire al decoratore @Injectable un metadato con chiave providerIn e valorizzata con un valore che indica quale parte dell’applicazione dovrà registrare e iniettare il service. Impostando providerIn con il valore root:

@Injectable({
   providerIn: 'root'
})

stiamo indicando che l’Application Injector ha l’onere di creare il service. In questo modo sarà disponibile per tutti i componenti dell’applicazione ed inettata nei componenti, a patto di registrarla.

Per poter importare un servizio all’interno di un component è necessario:

  • importare il riferimento al service
  • iniettarlo, impostandolo come variabile nel costruttore
import { service001 } from './service/service001.service'
...
...
export TestComponent{
   constructor(private servizio: service001){
   ....
   ....
   }
}

In questo post sono stati introdotti alcuni concetti chiave per lo sviluppo di un’applicazione Angular. Questi concetti verranno utilizzati quando analizzeremo lo sviluppo della parte client per l’applicazione ASPNet Core.