La gestione di form e inserimento dati è sempre un argomento molto delicato e complesso visto che ogni framework e ogni linguaggio gestire l’inserimento di dati in maniera differente. Su Angular esistono diversi modi più o meno ufficiali e più o meno efficienti, qui saranno esposti diversi modi di gestione degli input usabili a seconda delle diverse esigenze. Il metodo più semplice è gestire il valore con il metodo change, così da avere un metodo per la gestione del valore, l’esempio classico è:
lista: Person[]=[{id:'1',nome:'Alberto'},{id:'2',nome:'Andrea'},...] selezionato: Person=this.lista[0]; cambia($event: any): void { let input = $event.target; this.selezionato=this.lista.filter ( e => e.id===input.value)[0]; } ... <select (change)="cambia($event)"> <option *ngFor="let el of lista;" value="{{el.id}}">{{el.nome}} ({{el.id}})</option> </select> <span>Elemento selezionato: {{selezionato.nome}}</span>
questo metodo potrebbe sembrare arcaico e da evitare, in realtà è molto utile quando un input deve scatenare un evento senza l’uso di bottoni submit o simili, l’esempio più classico è un form per la selezione e il caricamento di un file il cui metodo di gestione può essere:
fileUploaded: File; uploadListener($event: any): void { //console.log("uploadListener"); this.fileUploaded = $event.target.files[0]; let readFile = new FileReader(); readFile.onload = (e) => { //load data & next step } readFile.readAsArrayBuffer(this.fileUploaded); }
Il metodo più semplice per creare forms composti è usare la tecnica del template-driven ben documentata nel sito ufficiale, prevede l’uso delle parole chiave NgModel e NgForm. Prima di tutto bisogna definire una classe che rappresenta il modello dei dati inseriti, per esempio:
class Person { constructor( public id: string, public nome: string, public age?: number ) { } }
Nella classe è possibile definire una proprietà modello con dei valori predefiniti e un metodo per la gestione del submit, nell’esempio viene solo gestita la visibilità del form:
ngOnInit() { this.newHero(); } formModel : Person ; newHero() { this.formModel = new Person('4','Daniele',42 ); } submitted = false; onSubmit() { this.submitted = true; }
Nel template bisogna usare ngForm per creare un riferimento e ngModel per ogni input per il recupero e la gestione dei valori, la validità dei form viene gestita grazie alle regole HTML: nell’esempio il nome è obbligatorio e il bottone si disabilità automaticamente quando il form diventa invalido a seconda delle regole indicate nel form:
<div [hidden]="submitted"> <form #personForm="ngForm" (ngSubmit)="onSubmit()"> <input type="text" class="form-control" id="name" required [(ngModel)]="formModel.nome" name="nome" /> <div>Nome inserito {{formModel.nome}}</div> <button type="button" (click)="newHero();">New Hero</button> <button type="submit" [disabled]="!personForm.form.valid">Submit</button> </form> </div>
Questa tecnica è molto usata quando le regole di validazione sono semplici, gli input sono numericamente poche e quando il comportamento di un form è semplice senza particolarità.
La tecnica più evoluta e usata nel mondo è la creazione dei form reattivi (traduzione dell’inglese reactive-forms), con questa tecnica è possibile creare una sincronia attiva tra oggetto nella classe e valore nel template, nella documentazione è definita come il metodo ufficiale per creare form in Angular. Per poterla attivare bisogna includere nel modulo specifico:
import { ReactiveFormsModule } from '@angular/forms'; ... @NgModule({ imports: [ // other imports ... ,ReactiveFormsModule ], });
Questa libreria/modulo standard di Angular prevedo l’uso della classe FormControl nel package standard angular/forms
che permette di collegare una proprietà nella classe ad un oggetto nel template proprio con la proprietà formControl. Nella classe per esempio è possibile definire una proprietà e un metodo per modificarne il valore
import { FormControl } from '@angular/forms'; ... nomeReattivo = new FormControl(''); updateNameReattivo() { this.nomeReattivo.setValue('Alberto'); }
Nel template è possibile creare il collegamento tra oggetto nella classe e tag di tipo input con una proprietà del framework Angular, inoltre sempre possibile creare recuperare il valore in maniera standard e creare eventi
Nome reattivo:<input id="name" type="text" [formControl]="nomeReattivo"> <div>Nome reattivo inserito: {{ nomeReattivo.value }}</div> <button type="button" (click)="updateNameReattivo()">Update nome reattivo</button>
Questa tecnica è molto veloce da implementare se il numero di campi è limitato, nel caso di form con diversi campi potrebbe essere noioso e sconveniente usare questa tecnica infatti esiste la possibilità di creare un “gruppo” di elementi di tipo FormControl: è necessario creare un oggetto di tipo FormGroup contenente la lista degli input definiti come nell’esempio precedente:
import { FormGroup, FormControl } from '@angular/forms'; import { Validators } from '@angular/forms'; ... personFormGroup = new FormGroup({ firstName: new FormControl('', [ Validators.required, Validators.minLength(3) ]), repeatName: new FormControl('', [this.comparisonValidator ]), mail: new FormControl('', Validators.pattern("[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,3}$") ) }); setValueFirstName(first){ this.personFormGroup.controls['firstName'].setValue(first); } onSubmitpPersonGroup() { // TODO: Use EventEmitter with form value console.log(this.personFormGroup.value); }
Nel template non si deve inserire una proprietà per ogni input ma si deve agganciare l’intero form al gruppo definito nella classe.
<form [formGroup]="personFormGroup" (ngSubmit)="onSubmitpPersonGroup()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Repeat Name: </label> <input id="last-name" type="text" formControlName="repeatName" /> <label for="last-name">E-Mail: </label> <input id="last-name" type="text" formControlName="mail" /> <button type="submit" [disabled]="!personFormGroup.valid">Submit personFormGroup</button> </form>
In questo semplice esempio è possibile notare che sono state introdotte delle regole di validazione nella definizione del FormGroup, la tecnica di form-reattivi permette di definire per ogni campo una regola di validazione che viene validato in maniera reattiva. La documentazione ufficiale descrive tutte le regole previste dal pacakge standard di angular ma è possibile definire regole di validazione personalizzate, per esempio per validare che l’utente abbia inserito correttamente due volte lo stesso valore nei due campi:
comparisonValidator(control: AbstractControl) : ValidationErrors{ if (!control.parent) return; const name=control.parent.value.firstName; const isValid=control.value===name; return isValid ? null : { 'myCustomError': 'This value is invalid' }; }
Nel template è possibile gestire la visualizzazione di messaggi a sconda se un errore di validazione è presente o meno, usando l’esempio di validazione dei nomi inseriti due volte è possibile visualizzare un messaggio se i nomi inseriti non coincidono:
<span [hidden]="!personFormGroup.get('repeatName').errors?.myCustomError"> I nomi devono coincidere</span>
Un esempio funzionante è disponibile al solito repository:
https://github.com/alnao/AngularReactNodeExamples/tree/master/AngularDatasetsFilms
Esiste una ulteriore tecnica per gestire l’inserimento dei dati e il passaggio dei dati dal template alla classe corrispettiva: questa tecnica prevede la definizione di un riferimento in un oggetto nel DOM che può essere usata in altri punti del template, la documentazione ufficiale è molto chiara a riguardo e prevede l’uso del carattere #
per la definizione di questi riferimenti, un semplice esempio di template che usa questa tecnica è:
<input #phone placeholder="phone number" /> <button type="button" (click)="callPhone(phone.value)">Call</button>
Questa tecnica è considerata deprecata da molti anche se, essendo stata molto usata in passato, si trova molto spesso nei progetti ed è ancora usata dai programmatori per la sua semplicità d’uso.