formularz dynamiczny
omówię:
- jak dynamicznie wyświetlić formularz w zależności od wprowadzonych danych
Na początku popracujmy nad wyglądem formularza. W app.component.scss dodaj następujący kod.
.main-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 60px;
}
.card {
margin: 10px;
}
.info-card {
display: flex;
width: 33%;
}
// <--------- początek
.form-card {
display: flex;
width: 66%;
flex-direction: column;
align-items: center;
.card-content {
width: 100%;
.form-container {
display: flex;
flex-direction: column;
align-items: center;
.form-input {
width: 50%;
}
}
}
}
// ---------> koniec
oraz w app.componet.html
<div class="main-container">
<ng-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>
</ng-container>
<ng-template #carInfo>
<app-card class="info-card"
[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 class="card-content"> <!-- dodanie klasy card-content -->
<form class="form-container" [formGroup]="clientForm" (submit)="countDiscount()"> <!-- dodanie klasy form-container -->
<mat-form-field class="form-input" appearance="outline"> <!-- dodanie klasy form-input -->
<mat-label>Nazwa klienta</mat-label>
<input matInput formControlName="name"/>
</mat-form-field>
<mat-form-field class="form-input" appearance="outline"> <!-- dodanie klasy form-input -->
<mat-label>Długość prowadzenia firmy ?</mat-label>
<input matInput formControlName="businessInMonths"/>
</mat-form-field>
<mat-form-field class="form-input" appearance="outline"> <!-- dodanie klasy form-input -->
<mat-label>Długość posiadanego prawa jazdy ?</mat-label>
<input matInput formControlName="licenseInMonths"/>
</mat-form-field>
<mat-checkbox formControlName="isUnder25">Kierowca poniżej 25 roku ?</mat-checkbox>
<button type="submit" mat-raised-button>
Oblicz zniżkę
</button>
</form>
</mat-card-content>
</mat-card>
</ng-template>
</div>
Po dodaniu stylowania formularz powinien prezentować się znacznie lepiej.
W chwili obecnej mamy już w miarę wyglądający formularz oraz możliwość wyboru danego samochodu. To czego brakuje to możliwość obsłużenia klienta biznesowego oraz klienta prywatnego. W obu przypadkach formularz powinien posiadać inne pola do wypełnienia oraz różne strategie wyliczania zniżki. W tej lekcji dokończymy jeszcze temat formularzy a w następnej podepniemy strategię do wyliczania zniżki.
Przejdź do form.service.ts i dodaj dwie metody. Jedna buduje formularz dla klienta prywatnego a druga dla klienta biznesowego.
import {Injectable} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
@Injectable({
providedIn: 'root'
})
export class FormService {
constructor(private formBuilder: FormBuilder) {
}
public createPrivateClientFormGroup(): FormGroup { // <------- tworzenie formularza dla klienta prywatnego
return this.formBuilder.group({
clientName: ['', Validators.required],
clientLastName: ['', Validators.required],
isUnder25: [''],
licenseInMonths: ['', [Validators.required, Validators.pattern('^[0-9]*$')]], // <--- dodanie walidacji wartości numerycznej
});
}
public createBusinessClientFormGroup(): FormGroup { // <------- tworzenie formularza dla klienta biznesowego
return this.formBuilder.group({
companyName: ['', Validators.required],
nip: ['', [Validators.required, Validators.pattern('^[0-9]*$')]],
businessInMonths: ['', [Validators.required, Validators.pattern('^[0-9]*$')]], // <--- dodanie walidacji wartości numerycznej
});
}
}
Następnie w katalogu form utwórz dwa komponenty, business-client-form oraz private-client-form poprzez wpisanie w konsoli ng g c business-client-form oraz ng g c private-client-form (będąc w podkatalogu form).
Dodaj następujący kod w business-client-form.component.ts
import {Component, OnInit} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {FormService} from '../form.service';
@Component({
selector: 'app-business-client-form',
templateUrl: './business-client-form.component.html',
styleUrls: ['./business-client-form.component.scss']
})
export class BusinessClientFormComponent implements OnInit {
public businessClientForm: FormGroup;
constructor(private formService: FormService) {
}
ngOnInit(): void {
this.businessClientForm = this.formService.createBusinessClientFormGroup();
}
public countDiscount() {
console.log(this.businessClientForm.controls);
}
}
oraz w private-client-form.component.ts
import {Component, OnInit} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {FormService} from '../form.service';
@Component({
selector: 'app-private-client-form',
templateUrl: './private-client-form.component.html',
styleUrls: ['./private-client-form.component.scss']
})
export class PrivateClientFormComponent implements OnInit {
public privateClientForm: FormGroup;
constructor(private formService: FormService) {
}
ngOnInit(): void {
this.privateClientForm = this.formService.createPrivateClientFormGroup();
}
public countDiscount() {
console.log(this.privateClientForm.controls);
}
}
Przejdź do business-client-form.component.html i połącz formularz z komponentu z szablonem
<form class="form-container" [formGroup]="businessClientForm" (submit)="countDiscount()">
<mat-form-field class="form-input" appearance="outline">
<mat-label>Nazwa firmy</mat-label>
<input matInput formControlName="companyName"/>
</mat-form-field>
<mat-form-field class="form-input" appearance="outline">
<mat-label>NIP</mat-label>
<input matInput formControlName="nip"/>
</mat-form-field>
<mat-form-field class="form-input" appearance="outline">
<mat-label>Długość prowadzenia firmy ?</mat-label>
<input matInput formControlName="businessInMonths" placeholder="w miesiącach"/>
</mat-form-field>
<button type="submit" mat-raised-button>
Oblicz zniżkę
</button>
</form>
oraz w private-client-form.component.html dla przypadku klienta detalicznego.
<form class="form-container" [formGroup]="privateClientForm" (submit)="countDiscount()">
<mat-form-field class="form-input" appearance="outline">
<mat-label>Imię klienta</mat-label>
<input matInput formControlName="clientName"/>
</mat-form-field>
<mat-form-field class="form-input" appearance="outline">
<mat-label>Nazwisko klienta</mat-label>
<input matInput formControlName="clientLastName"/>
</mat-form-field>
<mat-form-field class="form-input" appearance="outline">
<mat-label>Długość posiadanego prawa jazdy ?</mat-label>
<input matInput formControlName="licenseInMonths" placeholder="w miesiącach"/>
</mat-form-field>
<mat-checkbox formControlName="isUnder25">Kierowca poniżej 25 roku ?</mat-checkbox>
<button type="submit" mat-raised-button>
Oblicz zniżkę
</button>
</form>
w plikach business-client-form.component.scss oraz private-client-form.component.scss dodaj
.form-container {
display: flex;
flex-direction: column;
align-items: center;
}
.form-input {
width: 50%;
}
Mamy już dwa komponenty gotowe obsłużyć każdy z przypadków biznesowych. Musimy teraz przygotować główny komponent na obsługę tych przypadków. Chcemy uzyskać możliwość wyboru czy dany klient jest klientem biznesowym czy prywatnym. W pliku card.component.ts dodaj drugi przycisk o nazwie „FIRMA” oraz przycisk „WYBIERZ” zmień na przycisk „PRYWATNIE”.
Przejdź do pliku card.component.html i dodaj poniższy kod.
<mat-card>
<mat-card-header>
<mat-card-title>{{title}}</mat-card-title>
<mat-card-subtitle>{{subtitle}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{photoSource}}">
<mat-card-content>
<p>{{description}}</p>
</mat-card-content>
<mat-card-actions>
<ng-container *ngIf="!isCarSelected">
<button mat-raised-button color="warn" (click)="selectedCar(ClientType.BUSINESS)">FIRMA
</button>
<button mat-raised-button color="primary" (click)="selectedCar(ClientType.PRIVATE)">PRYWATNIE
</button>
</ng-container>
<button *ngIf="isCarSelected" mat-raised-button color="primary"
(click)="backToMainWindow()">WRÓĆ
</button>
</mat-card-actions>
</mat-card>
Zmieniła się także metoda selectedCar w tym momencie oczekuje ona parametru typu ClientType. Jest to prosty enum który posiada dwie wartości.
Stwórz katalog enums na poziomie katalogu app oraz dodaj w nim klasę client-type.enum.ts.
export enum ClientType {
PRIVATE = 'PRIVATE',
BUSINESS = 'BUSINESS'
}
Pozostaje jeszcze dostosować komponent card.component.ts
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {ClientType} from '../enums/client-type.enum';
import {OfferType} from '../interfaces/offer-type.interface';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss']
})
export class CardComponent {
@Input()
public photoSource: string;
@Input()
public title: string;
@Input()
public subtitle: string;
@Input()
public description: string;
@Input()
public identifier: string;
@Input()
public isCarSelected: boolean;
@Output()
public carSelected = new EventEmitter<OfferType>(); // <------ dodanie typu OfferType
@Output()
public returnToMainWindow = new EventEmitter();
public ClientType = ClientType;
public selectedCar(clientType: ClientType): void {
this.carSelected.emit({clientType, carVin: this.identifier}); // <----- dostosowanie metody pod typ OfferType
}
public backToMainWindow(): void {
this.returnToMainWindow.emit();
}
}
Ponownie musimy dostosować metodę selectedCar ponieważ przyjmuje ona inny typ niż poprzednio. EventEmitter carSelected wysła w tym momencie nie tylko nr vin samochodu ale także kontekst klienta.
Na poziomie katalogu app stwórz katalog interfaces i dodaj w nim plik offer-type.interface.ts
import {ClientType} from '../enums/client-type.enum';
export interface OfferType {
clientType: ClientType;
carVin: string;
}
Przejdź do pliku app.component.ts i dostosuj obsługę EventEmittera carSelcted
import {Component, OnInit} from '@angular/core';
import {CarService} from './car/car.service';
import {FormGroup} from '@angular/forms';
import {OfferType} from './interfaces/offer-type.interface';
import {Car} from './car/car.interface';
import {ClientType} from './enums/client-type.enum';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
selectedCar: Car;
cars: Car[] = [];
clientType: ClientType;
public ClientType = ClientType;
public ngOnInit(): void {
this.carService.fetchAvailableCars().subscribe(cars => {
this.cars = cars;
});
}
constructor(private carService: CarService) {
}
public carSelected(offerType: OfferType): void { // <----- zmiana typu parametru metody
this.selectedCar = this.cars
.find(car => car.vin === offerType.carVin);
this.clientType = offerType.clientType; // <------- przypisanie kontekstu klienta
}
public resetCarSection() {
this.selectedCar = null;
}
}
W metodzie carSelected przypisujemy wartość clientType, którą dostaliśmy z card.component, do lokalnej zmiennej this.clientType. Dzięki tej zmiennej będziemy wiedzieli którego komponentu form użyć, czy dla przypadku biznesowego czy dla przypadku klienta prywatnego.
Spójrz na kod w app.component.html
<div class="main-container">
<ng-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>
</ng-container>
<ng-template #carInfo>
<app-card class="info-card"
[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>
Klient
<ng-container *ngIf="clientType === ClientType.PRIVATE">prywatny</ng-container> <!--1-->
<ng-container *ngIf="clientType === ClientType.BUSINESS">biznesowy</ng-container> <!--1-->
wprowadź dane
</mat-card-title>
</mat-card-header>
<mat-card-content class="card-content">
<app-private-client-form *ngIf="clientType === ClientType.PRIVATE"> <!--2-->
</app-private-client-form>
<app-business-client-form *ngIf="clientType === ClientType.BUSINESS"> <!--2-->
</app-business-client-form>
</mat-card-content>
</mat-card>
</ng-template>
</div>
1) w tym miejscu dodajemy sprawdzenie w jakim kontekscie klienta działamy czy biznesowym czy detalicznym. W zależności od kontekstu wyświetlamy odpowiednią informację w nagłówku formularza.
2) tutaj także sprawdzamy rodzaj klienta. W zależności od rodzaju wyświetlamy odpowiedni komponent z odpowiednio przygotowanym formularzem.
Mam nadzieję, że wszystko poszło dobrze i zobaczyć następujące ekrany.
Dla klienta biznesowego
oraz klienta prywatnego