24 martie 2020

Angular - ziua 6 - Formulare

Formulare - 2 abordari:
- bazata pe template - Angular deduce obiectul formularului, din DOM
- reactiva - formularul este creat in mod programatic si este sincronizat cu DOM-ul

TEMPLATE

In app.module.ts, @NgModule -> imports -> FormsModule

<form (ngSubmit)="onSubmit(f)" #f="ngForm">
  <input type="text" id="username" name="username" ngModel>
</form>

In TypeScript:

onSubmit (form: NgForm) { // NgForm este un formular creat automat de Angular
     console.log(form.value['username']);
    // sau form.value.username
}

Altfel - daca vrei sa accesezi formularul nu numai la completarea lui, ci si inainte:
@ViewChild('f') signupForm: NgForm;
// ngModel aplicat pe tag-ul html iti da voie sa recuperezi obiectul in TypeScript
// onSubmit() poate deveni acum fara parametru de intrare

Cateva din alte proprietati furnizate de NgForm:
- dirty: daca formularul are vreun camp schimbat
- enabled, disabled
- valid, invalid
- touched: daca a fost facut click pe formular

Validare input:
- adaugare required in fisierul html, pe input-ul dorit
- alti validatori: email, lista completa aici => validitatea se va reflecta in atributele din NgForm
- Angular adauga dinamic clase css pe input-uri care reflecta starea, folosindu-le putem stiliza input-urile pentru a avertiza utilizatorul:

input .ng-invalid .ng-touched {
    border: 1px solid red;
}

Afisare mesaje de eroare:
<form (ngSubmit)="onSubmit(f)" #f="ngForm">
    <input type="text" id="myemail" name="myemail" ngModel #email="ngModel">
</form>
<span class="help-block" *ngIf="!email.valid && email.touched">Please enter valid email</span>

Inserare valori initiale:
defaultQuestion='pet';

<select id='secret' [ngModel]="defaultQuestion" name="secret">
  <option value="pet">What was your first pet?</option>
  <option value="teacher">What was your first teacher?</option>
</select>


Valori predefinite in TypeScript

@ViewChild('f') myForm: NgForm;
this.myForm.setValue ({
  username: 'myChoice',
  secret: 'pet'
  // trebuiesc adaugate toate campurile
});

// suprascrie doar campurile care ne intereseaza:
this.myForm.form.patchValue({
  userData: {
    username: 'something'
  }
});

Curatare formular: this.myForm.reset();

Two-way binding:
<textarea name="questionAnswer" rows="3" [(ngModel)]="answer"/>
<p> Your reply: {{answer}} </p>
// unde answer trebuie sa fie declarat ca empty string in TypeScript


ngModelGroup - pentru a se aplica la mai mult de un singur camp

<div ngModelGroup="userData" #userData="ngModelGroup">
<!-- campuri -->
</div>
<p *ngIf="!user.Data.valid && userData.touched">User data invalid!</p>


Butoane radio
<div class="radio" *ngFor="let gender of genders">
  <label>
    <input type="radio" name="gender" [value]="gender" ngModel> {{ gender }}
  </label>
</div>

ABORDAREA REACTIVA

- nu mai este nevoie de import FormsModule, se inlocuieste cu ReactiveFormsModule

signupForm: FormGroup;

ngOnInit() {
  this.signupForm = new FormGroup({
    'username': new FormControl(null),
    'email': new FormControl(null),
    'gender': new FormControl('male')
  });
}

onSubmit() { }

// Html:
<form (ngSubmit)="onSubmit()" [formGroup]="signupForm">
    <input type="text" id="username" formControlName="username">
    <input type="text" id="email" formControlName="email">
    <input type="radio" id="gender" formControlName="gender">
</form>

Validare prin metoda reactiva:  

this.signupForm = new FormGroup({
    'username': new FormControl(null, Validators.required), // referinta la metoda de validare
    'email': new FormControl(null, [Validators.required, Validators.email]),
    'gender': new FormControl('male')
});

Afisarea validarilor
<span *ngIf="!signupForm.get('username').valid && signupForm.get('username').touched">Please enter a valid username!</span>

Grupare reactiva
- elementele se aseaza intr-un <div formGroupName="userData">
- se actualizeaza span cu noua cale catre elementul validat:
<span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched">Please enter a valid username!</span>


FormArray









<div formArrayMode="hobbies">
  <h4>Your hobbies</h4>
  <button type="button" (click)="onAddHobby()">Add hobby</button>
  <div class="form-group" *ngFor="let hobbyControl of getControls(); let i = index">
    <input type="text" [formControlName]="i">
  </div>
</div>

// TypeScript:
ngOnInit() {
  this.signupForm = new FormGroup({
    'username': new FormControl(null),
    'email': new FormControl(null),
    'gender': new FormControl('male'),
    'hobbies': new FormArray([])
  });
}

getControls() {
  return (<FormArray>this.signupForm.get('hobbies')).controls;
}
onAddHobby() {
  const control = new FormControl(null, Validators.required);
  (<FormArray>this.signupForm.get('hobbies')).push(control); // explicit casting
}

Creare propriii validatori

forbiddenNames(control: FormControl): {(s: string): boolean} { // format output: {'mesaj de eroare': true}
  if (this.forbiddenUsernames.indexOf(control.value) !== -1) {
    return {'name is forbidden': true};
  }
  return null; // validation is successful
}

this.signupForm = new FormGroup({
    'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
    'email': new FormControl(null, [Validators.required, Validators.email]),
    'gender': new FormControl('male'),
    'age': new FormControl(null, Validators.pattern(/^[1-9]+[0-9]+$'/))
});
// binding-ul este pt a ne asigura ca this in metoda este ceea ce trebuie sa fie

Angular adauga erorile pe fiecare Control -> errors:
<span *ngIf="signupForm.get('userData.username').errors['name is forbidden']">This name is invalid!</span>

Validatori asincroni

forbiddenEmails(control: FormControl): Promise<any> | Observable<any> {
  const promise = new Promise<any>((resolve, reject) => {
    setTimeout(() -> {
      if (control.value === 'someAddress@yahoo.com') {
        resolve({'emailIsForbidden': true});
      } else {
        resolve(null);
      }
    }, 1500); // simulare asincronicitate
  });
  return promise;
}

// Html:
this.signupForm = new FormGroup({
    'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
    'email': new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmails.bind(this)), // al 3-lea arg este rezervat pt validatori asincroni
    'gender': new FormControl('male')
});

// TypeScript:
this.signupForm.valueChanged.subscribe(value => console.log(value));
this.signupForm.statusChanged.subscribe(status=> console.log(status)); // VALID, INVALID, PENDING (async)

setValue() si patchValue() sunt in continuare disponibile pe obiectul FormGroup.
reset() de asemenea.

Niciun comentariu: