8. Formularz - wprowadzenie

Temat artykułu:

Wprowadzenie do reaktywnych formularzy

Co omówię:

– wprowadzenie do reaktywnych formularzy

– jak zbudować formularz i dodać prostą walidację

– jak połączyć formularz i jego poszczególne elementy z HTML’em

– czym jest FormGroup oraz FormControl

W app.componet.html dodaj następujący kod który dodaje prawą sekcję na formularz.

<div class="container">
<div class="card-container" *ngIf="!selectedCar; else carInfo"> <!--5-->
<app-card *ngFor="let car of cars" class="card"
[identifier]="car.vin"
[title]="car.brand"
[subtitle]="car.model"
[photoSource]="car.photoSource"
[description]="'CENA: ' + car.price + ' ZŁ. VIN: ' + car.vin"
(carSelected)="carSelected($event)"> <!--6 i 7-->
</app-card>
</div>
<ng-template #carInfo>
<div class="card-container">
<app-card class="info-card-form"
[identifier]="selectedCar.vin"
[title]="selectedCar.brand"
[subtitle]="selectedCar.model"
[photoSource]="selectedCar.photoSource"
[description]="'CENA: ' + selectedCar.price + ' ZŁ. VIN: ' + selectedCar.vin"
[isCarSelected]="!!selectedCar"
(returnToMainWindow)="resetCarSection()"> <!--6 i 7-->
</app-card>

<mat-card class="form-card">
<mat-card-header>
<mat-card-title>Wprowadź dane klienta</mat-card-title>
</mat-card-header>
<mat-card-content>

</mat-card-content>
</mat-card>
</div>
</ng-template>
</div>

rozszerz app.componet.scss o nastepujące style

.container {
margin: 60px;
}

.card-container {
margin-top: 40px;
display: flex;
justify-content: space-between;
}

.card {
margin: 10px;
}

.info-card {
display: flex;
width: 33%;

&-form {
width: 60%;
}
}

.form-card {
width: 95%;
}

Po wyborze danego samochodu powinieneś ujrzeć następujący widok

Po prawej stronie dodaliśmy kartę. Jest to miejsce w którym będziemy budować formularz. Komponenty które wykorzystamy z Angular Material to input oraz checkbox. Dodaj do app.module.ts moduły z poniższego kodu.

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

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MatButtonModule,
MatCardModule, MatCheckboxModule,
MatExpansionModule,
MatFormFieldModule,
MatInputModule
} from '@angular/material';
import { ExpansionPanelComponent } from './expansion-panel/expansion-panel.component';
import { CardComponent } from './card/card.component';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
declarations: [
AppComponent,
ExpansionPanelComponent,
CardComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
MatExpansionModule,
MatFormFieldModule,
MatInputModule,
MatCardModule,
MatButtonModule,
HttpClientModule,
ReactiveFormsModule,
FormsModule,
MatCheckboxModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}

Do budowania formularza skorzystamy z tzw. reaktywnych formularzy. Podejście to cechuje się tym, że konstrukcja formularza, dodanie walidacji oraz zarządzanie formularzem odbywa się w komponencie/serwisie a nie w HTML’u. 

Dodaj folder form w katalogu app oraz stwórz serwis form.service.ts.

import {Injectable} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';

@Injectable({
providedIn: 'root'
})
export class FormService {

constructor(private formBuilder: FormBuilder) {
}

public createClientFormGroup(): FormGroup {
return this.formBuilder.group({
name: ['', Validators.required],
businessInMonths: ['', Validators.required],
isUnder25: [''],
licenseInMonths: ['', Validators.required],
});
}
}

Zbudowany formularz jest typu FormGroup i posiada wewnątrz siebie element np. name który jest typu FormControl. Jak widzisz poszczególny formControl może posiadać walidacje w tym przypadku required

Przejdź do app.component.ts i dodaj następujący kod.

import {Component, OnInit} from '@angular/core';
import {CarService} from './car/car.service';
import {CarModel} from './car/car.model';
import {FormService} from './form/form.service';
import {FormGroup} from '@angular/forms';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
selectedCar: CarModel;
cars: CarModel[] = [];
clientForm: FormGroup; // 1

public ngOnInit(): void {
this.carService.fetchAvailableCars().subscribe(cars => {
this.cars = cars;
});

this.clientForm = this.formService.createClientFormGroup();
}

constructor(private carService: CarService, private formService: FormService) {
}

public carSelected(vin: string): void {
this.selectedCar = this.cars
.find(car => car.vin === vin);
}

public resetCarSection() {
this.selectedCar = null;
}

public countDiscount() { // 2
console.log(this.clientForm.controls);
}
}

Dodaliśmy tutaj 

1) zmienną  clientForm  która będzie przetrzymywała referencję do formularza oraz metodę 

2) countDiscount() która będzie zwracała wartość zniżki po wypełnieniu formularza danymi oraz kliknięciu przycisku oblicz zniżkę.

Warto by było zaprezentować formularz w HTML’u. Przejdź do app.component.html  i dodaj poniższy kod.

<div class="container">
<div class="card-container" *ngIf="!selectedCar; else carInfo">
<app-card *ngFor="let car of cars" class="card"
[identifier]="car.vin"
[title]="car.brand"
[subtitle]="car.model"
[photoSource]="car.photoSource"
[description]="'CENA: ' + car.price + ' ZŁ. VIN: ' + car.vin"
(carSelected)="carSelected($event)">
</app-card>
</div>
<ng-template #carInfo>
<div class="card-container">
<app-card class="info-card-form"
[identifier]="selectedCar.vin"
[title]="selectedCar.brand"
[subtitle]="selectedCar.model"
[photoSource]="selectedCar.photoSource"
[description]="'CENA: ' + selectedCar.price + ' ZŁ. VIN: ' + selectedCar.vin"
[isCarSelected]="!!selectedCar"
(returnToMainWindow)="resetCarSection()">
</app-card>

<mat-card class="form-card">
<mat-card-header>
<mat-card-title>Wprowadź dane klienta</mat-card-title>
</mat-card-header>
<mat-card-content>
<form [formGroup]="clientForm" (submit)="countDiscount()"> <!--1-->

<mat-form-field appearance="outline">
<mat-label>Nazwa klienta</mat-label>
<label>
<input matInput formControlName="name"/> <!--3-->
</label>
</mat-form-field>

<mat-form-field appearance="outline">
<mat-label>Długość prowadzenia firmy ?</mat-label>
<label>
<input matInput formControlName="businessInMonths"/>
</label>
</mat-form-field>

<mat-form-field appearance="outline">
<mat-label>Długość posiadanego prawa jazdy ?</mat-label>
<label>
<input matInput formControlName="licenseInMonths"/>
</label>
</mat-form-field>

<mat-checkbox formControlName="isUnder25">Kierowca poniżej 25 roku ?</mat-checkbox>

<button type="submit" mat-raised-button> <!--2-->
Oblicz zniżkę
</button>
</form>

</mat-card-content>
</mat-card>
</div>
</ng-template>
</div>

1) tutaj zadziały się dwie rzeczy. Po pierwsze bindowanie formularza poprzez przypisanie do [formGroup] obiektu clientForm z komponentu. Po drugie przypisanie do zdarzenia submit na formularzu metody countDiscount()

2) aby powyższe przypisanie na submicie zadziałało poprawnie niezbędne jest zdefiniowanie w obrębie forma przycisku który będzie typu submit i to się właśnie zadziało w punkcie 2

3) aby połączyć poszczególny formControl który został zdefiniowany w form.service.ts (np. name, businnesInMonths itd.) wykorzystujemy elemnet formControlName

Po wybraniu poszczególnego auta powinieneś zobaczyć po prawej stronie niezbyt urokliwy formularz.

Stylowaniem oraz rozbudową funkcjonalności formularza zajmiemy się w kolejnej lekcji.

Po kliknięciu przycisku Oblicz zniżkę w konsoli deweloperskiej powinieneś zobaczyć zawartość formularza. Rozwiń objekt a następnie rozwiń elemnt name. Wartość name jest przechowywana w propsie value.