In the previous post, we have created our child modal components and a directive. Now it’s time to use them in our project.
Creating a new owner entity and using our child components and directives is our goal in this post.
If you want to see all the basic instructions and complete navigation for the .NET Core series, check outĀ the following link:Ā Introduction of the .NET Core series.
For the complete navigation and all the basic instructions of the Angular series, check out: Introduction of the Angular series.
The source codeĀ is availableĀ at GitHubĀ .NET Core, Angular, and MySQL. Part 13 – Source Code
This post is divided into several sections:
- Preparation For the Create Owner Component
- About Form Validation and ReactiveFormsModule
- Angular Form Validation HTML Part
- Angular Form Validation Component Part
- Inspecting Results
- Conclusion
Preparation For the Create Owner Component
Letās start by creating our component inside the owner folder. To do this, execute the Angular CLI
command:
ng g component owner/owner-create --skipTests
Then, we are going to modify the owner module by adding a new route:
{ path: 'create', component: OwnerCreateComponent }
When we click on the ācreateā link inside the
owner-list.component.html
file, we want the application to direct us to the creation page.So, let’s modify the <a>
tag inside the owner-list.component.html
file:
<a [routerLink]="['/owner/create']">Create owner</a>
About Form Validation and ReactiveFormsModule
Now, we can start writing code to create our entity and to validate the form. There are two types of validation in Angular: template-driven validation and reactive form validation. In our project, we are going to use reactive form validation because it is easier to read an HTML file. Furthermore, it doesnāt make HTML files so much “dirty” with too many code lines and all validation is in the component which makes it easier for maintenance.
Just before modifying our component, we need to modify the owner.module.ts
file. Let’s import the ReactiveFormsModule
because this is a module that supports the reactive form validation:
import { ReactiveFormsModule } from '@angular/forms';
imports: [ CommonModule, SharedModule, ReactiveFormsModule,
Additionally, we have to create a new interface:
export interface OwnerForCreation { name: string; dateOfBirth: string; address: string; }
Angular Form Validation Html Part
Modify theĀ owner-create.component.html
file:
<div class="container-fluid"> <form [formGroup]="ownerForm" autocomplete="off" novalidate (ngSubmit)="createOwner(ownerForm.value)"> <div class="form-horizontal card card-body bg-light mb-2 mt-2"> <div class="form-group row"> <label for="name" class="control-label col-md-2">Name of the owner: </label> <div class="col-md-5"> <input type="text" formControlName="name" id="name" class="form-control" /> </div> <div class="col-md-5"> <em *ngIf="validateControl('name') && hasError('name', 'required')">Name is required</em> <em *ngIf="validateControl('name') && hasError('name', 'maxlength')">Maximum allowed length is 60 characters.</em> </div> </div> <div class="form-group row"> <label for="dateOfBirth" class="control-label col-md-2">Date of birth: </label> <div class="col-md-5"> <input type="text" formControlName="dateOfBirth" id="dateOfBirth" class="form-control" appDatepicker (change)="executeDatePicker($event)" readonly/> </div> <div class="col-md-5"> <em *ngIf="validateControl('dateOfBirth') && hasError('dateOfBirth', 'required')">Date of birth is required</em> </div> </div> <div class="form-group row"> <label for="address" class="control-label col-md-2">Address: </label> <div class="col-md-5"> <input type="text" formControlName="address" id="address" class="form-control" /> </div> <div class="col-md-5"> <em *ngIf="validateControl('address') && hasError('address', 'required')">Address is required</em> <em *ngIf="validateControl('address') && hasError('address', 'maxlength')">Maximum allowed length is 100 characters.</em> </div> </div> <br><br> <div class="form-group row"> <div class="offset-5 col-md-1"> <button type="submit" class="btn btn-info" [disabled]="!ownerForm.valid">Save</button> </div> <div class="col-md-1"> <button type="button" class="btn btn-danger" (click)="redirectToOwnerList()">Cancel</button> </div> </div> </div> </form> <app-success-modal [modalHeaderText]="'Success message'" [modalBodyText]="'Action completed successfully'" [okButtonText]="'OK'" (redirectOnOK)="redirectToOwnerList()"></app-success-modal> <app-error-modal [modalHeaderText]="'Error message'" [modalBodyText]="errorMessage" [okButtonText]="'OK'"></app-error-modal> </div>
Now letās explain this code. In the form tag, we are creating theĀ
formGroup
with a name ownerForm
. This form group contains all the controls which we need to validate in our form. Moreover, with the (ngSubmit)
Ā we are calling a function when a user presses the submit button. As a parameter for that function, we are sending the ownerFormās value which contains all the controls with the data we need for the validation.There is aĀ formControlName
attribute inside every control. That attribute represents the control name which we are going to validate inside the ownerForm
and it is a mandatory attribute. Furthermore, in the <em>
tags we are displaying error messages if there are any. Errors will be written on the page only if the functions validateControl()
andĀ hasError()
returnĀ true as a result.
The function validateControl()
is going to check if the control is invalid and the hasError()
function is going to check which validation rules we are validating against (required, max lengthā¦). Both validateControl
and hasError
functions are our custom functions which we are going to implement in the component file. There is also a submit button that is going to be disabled until form becomes valid and a cancel button which is going to redirect the user away from the creation form.
Child Modals
Furthermore, we importĀ our child modal components inside this form to show success and error messages. As you might remember from the previous post, the success modal hadĀ the @Output
decorator named redirectOnOk
. This @Output
decorator is emitting the EventEmmiter
and in here we are subscribing to it with the event binding and assigning a function to it. The function redirectToOwnerList()
is going to be executed as soon as the user clicks on the OK button from the success modal window.
Excellent, letās continue on.
Angular Form Validation Component Part
Let’s modify the owner-create.component.ts
file:
import { Component, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { OwnerForCreation } from './../../_interfaces/owner-for-creation.model'; import { ErrorHandlerService } from './../../shared/services/error-handler.service'; import { RepositoryService } from './../../shared/services/repository.service'; import { Router } from '@angular/router'; import { DatePipe } from '@angular/common'; @Component({ selector: 'app-owner-create', templateUrl: './owner-create.component.html', styleUrls: ['./owner-create.component.css'] }) export class OwnerCreateComponent implements OnInit { public errorMessage: string = ''; public ownerForm: FormGroup; constructor(private repository: RepositoryService, private errorHandler: ErrorHandlerService, private router: Router, private datePipe: DatePipe) { } ngOnInit() { this.ownerForm = new FormGroup({ name: new FormControl('', [Validators.required, Validators.maxLength(60)]), dateOfBirth: new FormControl('', [Validators.required]), address: new FormControl('', [Validators.required, Validators.maxLength(100)]) }); } public validateControl = (controlName: string) => { if (this.ownerForm.controls[controlName].invalid && this.ownerForm.controls[controlName].touched) return true; return false; } public hasError = (controlName: string, errorName: string) => { if (this.ownerForm.controls[controlName].hasError(errorName)) return true; return false; } public executeDatePicker = (event) => { this.ownerForm.patchValue({ 'dateOfBirth': event }); } public createOwner = (ownerFormValue) => { if (this.ownerForm.valid) { this.executeOwnerCreation(ownerFormValue); } } private executeOwnerCreation = (ownerFormValue) => { const owner: OwnerForCreation = { name: ownerFormValue.name, dateOfBirth: this.datePipe.transform(ownerFormValue.dateOfBirth, 'yyyy-MM-dd'), address: ownerFormValue.address } const apiUrl = 'api/owner'; this.repository.create(apiUrl, owner) .subscribe(res => { $('#successModal').modal(); }, (error => { this.errorHandler.handleError(error); this.errorMessage = this.errorHandler.errorMessage; }) ) } public redirectToOwnerList(){ this.router.navigate(['/owner/list']); } }
Letās explain this code. As soon as a component mounts we are initializingĀ our
FormGroup
variable named ownerForm
with all the FormControls
. Pay attention that the keys in the ownerForm
object are the same as the names in theĀ formControlName
attribute for all input fields in a .html file, which is mandatory. Moreover, they have the same name as the properties inside the owner object (address, dateOfBirth, and name).When instantiating a new form control as a first parameter we are providing the value of control and as a second parameter the Validators array, which holds all the validation rules for our controls.
In the validateControl()
method, we are checking if the current control is invalid and touchedĀ (we donāt want to show an error if the user didnāt place the cursor inside control at all).Ā Furthermore, theĀ hasError()
function will check which validation rule the current control has violated.
Our dateOfBirth
control has the appDatepicker
directive, which attaches datepicker inside that input control. Selecting the date won’t patch a value for that control inside theĀ ownerForm
, so we need to do that manually inside the executeDataPicker()
function.
Creation Process and DatePipe
The createOwner just calls the function that sends the HTTP request to the server. Notice that in this example using the date pipe is not restricted only to HTML files. Of course, to make it work inside a component, we need to import the DatePipe
pipe and inject it inside the constructor. We also need to import the DatePipe
inside the app.module.ts
file and to place it inside the “providers
” array.
If you look at the redirectToOwnerList()
function, you are going to see the familiar code for navigating back to the previous component. There is another way of doing this by importing theĀ Location
from theĀ @angular/common
and injecting it inside the constructor and then just calling theĀ back()
function on that injected property (location.back())
. What you decide to use is totally up to you.
After all, we are extracting values from theĀ ownerForm
, and sending a POST request to our server.
Now just modify our root CSS file (styles.css), to show <em>
messages with the red color and the bold style and to wrap the inputs in the red colorĀ if they are invalid:
em{ color: #e71515; font-weight: bold; } .ng-invalid.ng-touched{ border-color: red; }
Inspecting Results
Form with required errors:
Form with additional errors:
Valid form:
The owner created successfully:
When you click the OK button, you will be redirected to the owner-list page and the new owner is going to be on the list.
In the ErrorHandler service, we are going to modify the handleOtherError
function:
private handleOtherError(error: HttpErrorResponse){ this.createErrorMessage(error); $('#errorModal').modal(); }
Now if the error other than 500 or 404 appears, we are going to show a modal message to the user:
Conclusion
By reading this post you have learned:
- About different validation types in the Angular
- How to use reactive form validation in the HTML file
- How to use reactive form validation in the component file
- The way to create a new entity on the client-side
Thank you for reading the post, hopefully, it was helpful to you.
In the next part of the series, we are going to write the update part of the project, by sending the POST request towards our server.