In the previous post, we created our modal components. Now it’s time to use them in our project.
Creating a new owner entity and also validating a form is our goal in this article
For the complete navigation and all the basic instructions of the Angular series, check out: Introduction of the Angular series.
Let’s start.
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 --skip-tests
Then, we are going to modify the owner routing 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 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 an HTML file is easier to read. 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 we modify 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'; ... @NgModule({ ... imports: [ CommonModule, OwnerRoutingModule, SharedModule, ReactiveFormsModule ]
Additionally, we have to create a new interface:
export interface OwnerForCreation{ name: string; dateOfBirth: string; address: string; }
Angular Form Validation HTML Part
Let’s continue by modifying the owner-create.component.html
file:
<div class="container-fluid"> <form [formGroup]="ownerForm" autocomplete="off" novalidate (ngSubmit)="createOwner(ownerForm.value)"> <div class="card card-body bg-light mb-2 mt-2"> <div class="row mb-3"> <label for="name" class="col-form-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="mb-3 row"> <label for="dateOfBirth" class="col-form-label col-md-2">Date of birth: </label> <div class="col-md-5"> <input type="text" formControlName="dateOfBirth" id="dateOfBirth" class="form-control" readonly bsDatepicker/> </div> <div class="col-md-5"> <em *ngIf="validateControl('dateOfBirth') && hasError('dateOfBirth', 'required')">Date of birth is required</em> </div> </div> <div class="mb-3 row"> <label for="address" class="col-form-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="mb-3 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> </div>
Now let’s explain this code. In the form
element, we create the formGroup
with the ownerForm
name. This form group contains all the controls we need to validate in our form. Moreover, with the (ngSubmit)
we call a function when a user presses the submit button. As a parameter for that function, we send 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 display error messages if there are any.
One thing to pay attention to is the DateOfBirth input element. Here we use the bsDatepicker
directive from ngx-bootstrap
, to attach the datepicker
to this input component. For this to work, we have to add the BsDatepickerModule
module inside the owner.module.ts
file:
... import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; ... @NgModule({ ... imports: [ CommonModule, OwnerRoutingModule, SharedModule, ReactiveFormsModule, BsDatepickerModule.forRoot() ]
There are a lot more functionalities that we can use with ngx-bootstrap’s date picker, and to learn about them, you can read this documentation.
About the Errors and Buttons on the Form
Errors will be written on the page only if the functions validateControl()
and hasError()
return true as a result.
The validateControl()
function 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 the custom functions that we are going to implement in the component (.ts) file. There is also a submit button that is going to be disabled until the form becomes valid and a cancel button that is going to redirect the user away from the creation form.
Angular Form Validation Component (.ts) Part
Now, we have to implement the logic for all those functions called in the template file. We are going to add a lot of logic to the owner-create.component.ts
file, so we are going to try to break the explanation into multiple parts. Let’s start with the imports:
import { SuccessModalComponent } from './../../shared/modals/success-modal/success-modal.component'; import { DatePipe } from '@angular/common'; import { Router } from '@angular/router'; import { ErrorHandlerService } from './../../shared/services/error-handler.service'; import { OwnerRepositoryService } from './../../shared/services/owner-repository.service'; import { Component, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Owner } from 'src/app/_interfaces/owner.model'; import { HttpErrorResponse } from '@angular/common/http'; import { OwnerForCreation } from 'src/app/_interfaces/ownerForCreation.model'; import { ModalOptions, BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
A lot of import statements, and we will see throughout the code why we use each of those.
Now, let’s inspect another part of the file:
export class OwnerCreateComponent implements OnInit { errorMessage: string = ''; ownerForm: FormGroup; bsModalRef?: BsModalRef; constructor(private repository: OwnerRepositoryService, private errorHandler: ErrorHandlerService, private router: Router, private datePipe: DatePipe, private modal: BsModalService) { } ngOnInit(): void { 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)]) }); } }
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.
Handling Errors
Now, we are going to add those two functions that we call in our template file to handle validation errors:
validateControl = (controlName: string) => { if (this.ownerForm.get(controlName).invalid && this.ownerForm.get(controlName).touched) return true; return false; } hasError = (controlName: string, errorName: string) => { if (this.ownerForm.get(controlName).hasError(errorName)) return true; return false; }
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 the control at all). Furthermore, the hasError()
function will check which validation rule the current control has violated.
Creation Process, DatePipe, and Modal Invocation
To continue, we are going to add the createOwner function that we call in the (ngSubmit) event:
createOwner = (ownerFormValue) => { if (this.ownerForm.valid) this.executeOwnerCreation(ownerFormValue); }
It accepts the form value, checks if the form is valid, and if it is calls another private function passing the form value as a parameter.
That said, let’s create that private function:
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.createOwner(apiUrl, owner) .subscribe({ next: (own: Owner) => { const config: ModalOptions = { initialState: { modalHeaderText: 'Success Message', modalBodyText: `Owner: ${own.name} created successfully`, okButtonText: 'OK' } }; this.bsModalRef = this.modal.show(SuccessModalComponent, config); this.bsModalRef.content.redirectOnOk.subscribe(_ => this.redirectToOwnerList()); }, error: (err: HttpErrorResponse) => { this.errorHandler.handleError(err); this.errorMessage = this.errorHandler.errorMessage; } }) }
Here we create a new OwnerForCreation
object that we are going to send to the API with our POST request. 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 file, we need to import the DatePipe
inside the app.module.ts
file and to place it inside the “providers
“ array:
import { DatePipe } from '@angular/common'; @NgModule({ declarations: [ ... ], imports: [ ... ], providers: [DatePipe], bootstrap: [AppComponent]
Once we create the ownerForCreation
object, we create the apiUrl
, and call the createOwner
repository function. Now, since we are using the OwnerForCreation
type, and not the Owner
type, we have to modify the type inside the repository function:
public createOwner = (route: string, owner: OwnerForCreation) =>
Good. Let’s get back to our logic.
Inside the subscribe
function – with the next
property – we accept the success response from the API, create the initial state object for our success modal, and then show the modal by providing the SuccessModalComponent
and the config
object as parameters. Additionally, we use the bsModalRef
object to subscribe to the redirectOnOk
event that we emit from the SucessModalComponent once we click the Ok button.
Redirection
With this subscription, we are calling the redirectToOwnerList
function. We call the same function by clicking on the Cancel
button in our form. So, let’s add that function:
redirectToOwnerList = () => { this.router.navigate(['/owner/list']); }
This is 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.
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
Now, if we navigate to the CreateOwner
component, once we click inside each input element but leave it empty, we are going to see our error messages:
Of course, we can test the max length validation as well.
Once we populate all the fields and click the Save button, we are going to see the success modal message:
When we click the OK
button, we will be redirected to the owner-list page and the new owner is going to be on the list.
The source code for the server project that you can use for this article is here.
Showing Error Modal Component
In the ErrorHandler
service, we are going to modify the handleOtherError
function:
private handleOtherError = (error: HttpErrorResponse) => { this.createErrorMessage(error); const config: ModalOptions = { initialState: { modalHeaderText: 'Error Message', modalBodyText: this.errorMessage, okButtonText: 'OK' } }; this.modal.show(ErrorModalComponent, config); }
Of course, we have to add imports in the same file:
import { ErrorModalComponent } from './../modals/error-modal/error-modal.component'; import { BsModalService, ModalOptions } from 'ngx-bootstrap/modal';
Now if an error other than 500 or 404 appears, we are going to show a modal message to the user. We can force an error, to test this behavior, by modifying the CreateOwner action in the Web API project:
[HttpPost] public IActionResult CreateOwner([FromBody] OwnerForCreationDto owner) { return BadRequest("Bad request from the server while creating owner"); ...
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 PUT request to our server.
Hi Marinko,
possible typo – I believe there is a typo in the page under ‘About Form Validation and ReactiveFormsModule’ where you say: “Just before modifying our component, we need to modify the owner.model.ts file.”
I believe this should actually be the owner.module.ts file
Thank you for an excellent tutorial
Thank you very much for this suggestion. Yes it is a typo. And it has been here for couple of years 🙂 🙂
I expect anyone that noticed just checked against the source. I had actually added the code to the correct file, but was having the same problem as Andre Labuschagne so was comparing everything. I too have that issue wiith ‘Property modal does not exist on type ‘JQuery‘. I have installed jQuery, jQueryui and Bootstrap, and their types at least twice, and have the 3 types in tsconfgi.app.json, and the styles & scripts in angular.json. Following Andre’s suggestion allowed ng serve to compile/run without error, but doesn’t show the form. Quite puzzling.
Hi Marinko,
Thanks for the excellent series. I have looked at quite a few Angular tutorials now and this is by far the best I have located so far. There is something that I had issues with and I was hoping you could let me know if I did anything wrong:”
In the owner-create.component there is section for the success modal which displayed an error. The error was “Property ‘modal’ does not exist on type ‘JQuery
After spending some time looking at StackOverflow I found a solution but I’m not too sure if this is the correct way to solve:
($(‘#successModal’) as any).modal();
I had to do something similar with my datepicker as well. I have checked all of the files in my solution comparing them to your files on GitHub and as far as I can tell everything is correct.
Thanks again for all the hard work!
Hello Andre. First of all, thank you very much for the kind words. It means a lot to me. Regarding your issue, it looks like you are missing the JQuery type installation because it doesn’t recognize “$” as the JQuery reference. I am not sure that is the case, but it looks like it. Or maybe you have it installed but missed the tsconfig.app.json modification. Again I am not sure, it just looks like it.
Hi Marinko,
Thank you for your quick response. I have just reinstalled jquery and @types/jquery and checked the styles and scripts section on angular.json as well as the types in tsconfig.app.json. It all matches with your Github code for part 13.
I have just checked my Angular CLI version and I’m on version 11.0.5 with Node 14.15.3 – is it possible that there were changes between Angular 9 and Angular 11 which changed the way jquery is implemented? In CLI 11 there is an extra question when you create a new project:
– Do you want to enforce stricter type checking and stricter bundle budgets in the workspace (Defaults to No)
– Would you like to add Angular routing (Defaults to No)
– Which stylesheet format wuld you like to use (Defaults to CSS)
I don’t know if either of the first two options would have resulted in my issue.
Hello Andre. These settings shouldn’t cause you that problem. The last two questions were there since earlier Angular versions. The first question is a new one but again it doesn’t affect the JQuery usage. Moreover, I just created a new Angular 11 app and installed JQuery and types, and used $ with some functions without a single problem. I am really not sure why do you have that problem.
Thank you, you have gone above and beyond to try and help me out. I will go through everything again, because I must have missed something somewhere.
Hi,
Thanks for the great tutorial. I am working my way through this tutorial and am getting an error when I try to create a new owner. The error message modal comes up, but it just says [object ProgressEvent] in the text area. I used the version from your repository and am still getting this error. Any suggestions?
Hi Chirs. I am not sure why do you get this error. What is more strange is that you say that our code doesn’t work as well. I have updated this entire project to use Angular 9 two months ago, so it should be working just fine. I will check that.
Let me just say, I’ve just tested our code, it works perfectly. Are you sure that your server is started at all. I have just managed to reproduce the same error once I shut down the server side application.
Hi Marinko,
Thanks for checking into these matters. I just verified that it was indeed your code that I was running and that the ng serve command had been executed. Another strange thing that happens is when I click inside the name textbox on the form, I get an empty error message modal. Does that give you any clue to what the issue could be? I also notice that there is no data when I go to the owner list. I figured at first it might just be because I had not set up the Create Owner functionality yet, but now I’m not sure. I figured I’d just list all of the abnormalities I’ve encountered (maybe doing so would give you a hint as to what was going on over on my end :P).
Thanks again!
I really don’t know what to say on that. I’ve tested all you have mentioned above, and in our source code everything works. And that code is the code from GitHub, so the same one you can download. As I said in my previous comment, the [object ProfessEvent] error was shown only when I turned off the server app.
If you want, you can always share your angular code with me on GitHub or wherever, I can spare some time to look at it, but not too much since consultations are not cheap and easy and trust me, we are preety bussy here with articles, videos and everything.
Hi Marinko,
Thank you for being willing to look at the code in spite of how busy you are. However, I did figure out the issue. I started the tutorial at a point other than in the beginning because I was just working on the Angular part of things at the time. I guess I was under the assumption that the database was going to get set up at a later time and that the data that had been showing up was stubbed somewhere in one of the links in your code. I did not realize that for the server stuff you were referring to things that were set up before that. After going back to the beginning of your tutorial, I saw several steps that I had skipped in the configuration and am working through them now. I believe that once I take these steps everything will work just fine.
You are right. Just do it from the beginning and everything should work just fine.
Hey Marinko,
really thanks for given tutorial, I am new in angular and it is really to much helpful for me. But I have some queries.
In post request example we display success or error message based on which error occur. But if we send data to the serve and server give us feedback data like your are success login or username. So for this situation how we do it. Means how we send data to server as well as how to get data to the server
Hello Dhruval. Did you read the entire series or just this article? In the previous parts, we have explained how to get data from the API and how to display that data on the screen. Regarding the registration and login forms, you will have to add some custom logic for the error handling, especially for the login logic, unless you want to show error messages in the modal window. Basically, in the error handling service, you can check the route and the status code of the response, and based on that to decide whether you want to show the modal window or just to display the error message on the same screen.
Hi Marinko,
Thanks for this tutorial, great job!
I’m not able to run your code for part 13. I’m getting the following when clicking on “Create Owner”:
ERROR Error: Uncaught (in promise): Error: No provider for DatePipe!
Error: No provider for DatePipe!
at injectionError (core.es5.js:1169)
at noProviderError (core.es5.js:1207)
at ReflectiveInjector_._throwOrNull (core.es5.js:2649)
at ReflectiveInjector_._getByKeyDefault (core.es5.js:2688)
at ReflectiveInjector_._getByKey (core.es5.js:2620)
at ReflectiveInjector_.get (core.es5.js:2489)
at resolveNgModuleDep (core.es5.js:9492)
at NgModuleRef_.get (core.es5.js:10562)
at resolveNgModuleDep (core.es5.js:9492)
at NgModuleRef_.get (core.es5.js:10562)
at resolvePromise (zone.js:809)
at resolvePromise (zone.js:775)
at eval (zone.js:858)
at ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.es5.js:3881)
at ZoneDelegate.invokeTask (zone.js:420)
at Zone.runTask (zone.js:188)
at drainMicroTaskQueue (zone.js:595)
at ZoneTask.invokeTask [as invoke] (zone.js:500)
at invokeTask (zone.js:1517)
Any ideas?
Cheers,
Doug
It was happening with my code, following your tutorial, but then I’ve cloned your repository and I’m still getting the same error, so I’m discarding any typo on my side…
Hello Douglas. I found the error. I was transfering the server side to the 3.0 version and because of that I had to include DatePipe in this component. But I forgot to mention, and to add DatePipe in the app.module.ts file:
import { DatePipe } from '@angular/common';
and to provide it in the providers array:
providers: [
EnvironmentUrlService,
RepositoryService,
ErrorHandlerService,
DatePipe
],
just do that, and everything will work. I will update the code and article ASAP.
I hope this will help you.
Thank you a lot for the suggestion. Best regards.
Hi Marinko,
Your blog is always help us to implement great things in project, can you please create the blog series for Crud operation in 1 page. Like for add and edit one model. On the listpage list with edit and delete button. and on the delete button model with confirmation, and models after deleting record.
this will really help for candidate like me.
Also i have one question. Why are you used jquery ui when your are installing boostrap.
Hello Omkar. Thank you for reading our articles. About your suggestion, well we are in middle of something right know but we will see in the future. But trust me when I say this, if you don’t make your hands dirty, you will never learn. I am pretty sure if you have gone through all the parts of this series, you have enough knowledge to do it by yourself 😀
About your question, I have used JQuery for the datapicker control, and it helped me to show why should we use and how to use directives.
Hi Marinko,
With the help of your article and and some other article. I have created one component with crud operation with modal window for add and edit. to implement this i have used the formbuilder. except display list i am not able to do any operation. when click on edit or delete button nothing happen. on click of add button modal form is open but after data entry hit on submit button no data pass to component ts.
I have uploaded my project on below url.
https://github.com/Mhaiskaromkar/DemoAngularApplication/
Please check and let me know what is the issue. how to resolve this issue.
Thanks in advance,
Omkar.
Hello Omkar. First of all I would never recommend to create this kind of component structure. One component should do only one thing not all of the operations. So I would create a modal child component for every action (add, update, delete) and every modal component would do all the necessary things required for that action. I know what you tried to do, to create one generic to cover all the crud operations, but this is not good at all. You can read this article http://34.65.74.140/angular-best-practices/ to learn more about proper coding structure.
Second thing I don’t have the server part of your application so I can’t test it entirely. But as much as I could see, you have the input type with attribute formcontrolname (UpdateBy) and not formControlName (with capital C), thus your mapping is not working for that input control. Maybe this is causing you trouble.
You don’t have an endpoint for your deleteProductCategory() function, thus you cant start your delete modal.
As much as I could test it, without the server part, and with formControlName change, all worked ok. The code is accessing all the way to the repository part, and there it break, because I didn’t have the server to accept that request.
So try to change that attribute and try to debug your code afterwords.
All the best.
Hi Marinko,
Thanks for your quick reply. The issues is getting resolved just now. May be capital c in form control name was issue. I will check it. Server side code is working fine. I have followed your core articles. So server side code is not issue for me.
Also thank for providing valuable information. I am also practising different way of implementing functionality as I am new too angular.
It will help for interview preparation.
Thanks you once again
Hey, i try create the ‘/owner/create’, but when i clicked on link (create owner) nothing happened , i looked your code, but i don’t see nothing diferent
Hi Leonardo. First, thank you very much for reading our posts. I am sorry that you have a problem with your code, but let me try to help you. I have checked out my uploaded code and all is working fine. Now I have couple of things for you to check out:
1) Did you use the Angular CLI command to create OwnerCreateComponent (if you haven’t done that way maybe you don’t have OwnerCreateComponent imported inside the Owner module file in the declarations array)
2) Have you double checked your anchor tag in the owner-list.component.html file. It should look like this:
[routerLink]=”[‘/owner/create’]”>Create owner
3) Have you double checked the owner.module.ts file for the RouterModule.forChild code. You need to have reference to the OwnerCreateComponent in the array: { path: ‘create’, component: OwnerCreateComponent }
If all of these steps are checked, and you haven’t find the error yet, you could sent me your code or you may check the error message, if there is any, in the developer tools.
If you find a time, inform me about the result. I hope I helped you a bit.
Hello Marinko. Great tutorial and thank you for all amazing knowledge you share with us over here.
I have the same problem on my project. I am doing it exactly as it shows on this tutorial but still nothing happens when I click on Create Owner. I have noticed that when I place my cursor on the link it shows on the bottom of my browser as http://localhost:4200/owner/create so I believable that the route is correct.
What other thing can cause this to happen ?
I have followed the guide over and over and I can’t still find my problem.
I also double checked what you recommended in this answer. Please help , I am learning this Angular from scratch.
Thank you very much in advance .
Hi Alex. Is there any chance for you to compare your solution with mine, which is on the GitHub (link is in the article). I really can’t say more then I did in my previous response. Basically, the navigation part is covered in the first section of this article.
Thank you very much. I was able to find the problem
It may help others.
Open file app.module.ts
scroll down until providers array.
add DatePipe
Code must look like this
providers: [DatePipe],
Hope this could help.