avatarFuji Nguyen

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

16318

Abstract

s text-light'</span>, delay: <span class="hljs-number">2000</span>, autohide: <span class="hljs-literal">true</span>, }); }

<span class="hljs-comment">// convenience getter for easy access to form fields</span>
<span class="hljs-keyword">get</span> f() { <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.entryForm.controls; }

}</pre></div><p id="aced">The above code is an Angular component called <code>PositionDetailComponent</code>. It represents the TypeScript implementation of the component and includes various imports and class properties and methods. Let's go through the code section by section:</p><ol><li>The imports at the top of the code include necessary dependencies from Angular and external libraries. These imports are used for various functionalities such as routing, form handling, validation, services, and more.</li><li>The component decorator is used to specify the component’s selector, template URL, and style URLs. It also includes the <code>standalone: true</code> property, which indicates that this component can be used independently without any parent component.</li><li>Inside the component class definition, there are various class properties declared. Some notable properties include <code>formMode</code> for tracking the form mode (New or Edit), <code>sub</code> for storing the subscription to route params, <code>id</code> for storing the ID of the position, <code>entryForm</code> for the reactive form group, <code>error</code> for storing error messages, <code>position</code> for storing position data, <code>isAddNew</code> for determining if it's a new position, and <code>submitted</code> for tracking form submission.</li><li>The constructor initializes the form by calling the <code>createForm()</code> method and injects the required services like <code>ToastService</code>, <code>ActivatedRoute</code>, <code>FormBuilder</code>, <code>ApiHttpService</code>, <code>ApiEndpointsService</code>, and <code>ModalService</code>.</li><li>The <code>ngOnInit()</code> method is called when the component is initialized. It subscribes to the route params and retrieves the <code>id</code> parameter. Based on the existence of the <code>id</code>, it sets the form mode and calls the <code>read()</code> method to fetch the position data if in edit mode.</li><li>The component includes methods for handling button clicks and CRUD operations. <code>onCreate()</code> handles the create button click and calls the <code>create()</code> method to submit the form data. <code>onUpdate()</code> handles the update button click and calls the <code>put()</code> method to update the position data. <code>onDelete()</code> handles the delete button click and prompts the user for confirmation using the <code>ModalService</code>. If confirmed, it calls the <code>delete()</code> method to delete the position data.</li><li>The <code>read()</code>, <code>delete()</code>, <code>create()</code>, and <code>put()</code> methods make HTTP requests using the <code>ApiHttpService</code> to perform CRUD operations on the position data. They handle the response and error accordingly.</li><li>The <code>createForm()</code> method is used to create the reactive form using the <code>formBuilder</code>. It defines form controls with initial values and validators.</li><li>The <code>showToaster()</code> method is used to display a toast message using the <code>ToastService</code>. It shows a success message with a specific title and message.</li><li>The <code>get f()</code> method is a getter property that provides easy access to the form fields in the template using <code>entryForm.controls</code>.</li></ol><p id="abe7">Overall, this component manages the creation, reading, updating, and deleting (CRUD) operations for a position entity using a reactive form. It communicates with an API through the <code>ApiHttpService</code> and displays toast messages for success or failure using the <code>ToastService</code>.</p><p id="fb8e">If you are new to the Reactive Form, pay special attention to the block of code below</p><div id="05c3"><pre><span class="hljs-keyword">private</span> <span class="hljs-title function_ invoke__">createForm</span>() { this.entryForm = this.formBuilder.<span class="hljs-title function_ invoke__">group</span>({ <span class="hljs-attr">id</span>: [<span class="hljs-string">''</span>], <span class="hljs-attr">positionNumber</span>: [<span class="hljs-string">''</span>, Validators.required], <span class="hljs-attr">positionTitle</span>: [<span class="hljs-string">''</span>, Validators.required], <span class="hljs-attr">positionDescription</span>: [<span class="hljs-string">''</span>, Validators.required], <span class="hljs-attr">positionSalary</span>: [<span class="hljs-string">''</span>, RxwebValidators.<span class="hljs-title function_ invoke__">numeric</span>({ <span class="hljs-attr">allowDecimal</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">isFormat</span>: <span class="hljs-literal">false</span> })], }); }</pre></div><p id="f32a">The method <code>createForm()</code> creates a form using the <code>formBuilder</code> service from Angular's Reactive Forms module. Let's break down the code step by step:</p><ol><li>The method is declared as <code>private</code>, which means it can only be accessed within the same class.</li><li>Inside the method, a form is created using <code>this.formBuilder.group()</code>. The <code>formBuilder</code> is an instance of <code>FormBuilder</code> class provided by Angular's Reactive Forms module.</li><li>The form group is initialized with an object literal that defines the form controls. Each form control is represented by a key-value pair, where the key is the name of the control and the value is an array with two elements.</li><li>The first element of each control’s array is the initial value of the control. In this case, the <code>id</code> control is initialized with an empty string <code>''</code>.</li><li>The second element of each control’s array is an array of validators to apply to the control. Validators are functions used to validate user input. In this code, the <code>Validators.required</code> validator is used to make the <code>positionNumber</code>, <code>positionTitle</code>, and <code>positionDescription</code> controls required.</li><li>The <code>positionSalary</code> control is configured with a custom validator called <code>RxwebValidators.numeric()</code>. This validator is from the <code>RxwebValidators</code> library, which is used to validate numeric inputs. It allows decimal numbers (<code>allowDecimal: true</code>) and does not enforce a specific format (<code>isFormat: false</code>).</li><li>After creating the form group, it is assigned to the <code>entryForm</code> property of the class. This property is assumed to be declared somewhere in the class.</li></ol><p id="b07d">Overall, this method sets up a form with five controls: <code>id</code>, <code>positionNumber</code>, <code>positionTitle</code>, <code>positionDescription</code>, and <code>positionSalary</code>, with appropriate validators applied to some of the controls. The form can be used for data entry and validation in an Angular application.</p><h1 id="052d">Part 2: Reactive Form HTML Markup Walkthrough</h1><p id="c4c8">Below is an example of an Angular reactive form that includes multiple form controls with validation taken from source code file <a href="https://github.com/workcontrolgit/TalentManagement-Client-Angular16/blob/78ec065b40dc7b160d131c2193beb8ed1a8d6970/src/app/features/position/position.component.html">position.component.html</a></p><div id="aa72"><pre><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container-fluid"</span>></span> <span class="hljs-comment"><!-- HTML form mark up --></span> <span class="hljs-tag"><<span class="hljs-name">form</span> [<span class="hljs-attr">formGroup</span>]=<span class="hljs-string">"entryForm"</span> <span class="hljs-attr">novalidate</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-header"</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-start"</span>></span><span class="hljs-tag"><<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-secondary"</span>></span>Position<span class="hljs-tag"></<span class="hljs-name">h3</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-end"</span>></span> <span class="hljs-comment"><!-- HTML markup for form mode New or Edit --></span> <span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn text-dark"</span> [<span class="hljs-attr">routerLink</span>]=<span class="hljs-string">"['/position']"</span>></span><span class="hljs-tag"><<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fa fa-arrow-left"</span>></span><span class="hljs-tag"></<span class="hljs-name">i</span>></span> Back<span class="hljs-tag"></<span class="hljs-name">a</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-body"</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"alert alert-danger"</span> [<span class="hljs-attr">hidden</span>]=<span class="hljs-string">"!error"</span> <span class="hljs-attr">translate</span>></span> Position Number, Title, Description or Salary incorrect. <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-group"</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"id"</span>></span>Id<span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"d-block mb-3"</span>></span> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"id"</span> [<span class="hljs-attr">ngClass</span>]=<span class="hljs-string">"{ 'is-invalid': f.id.errors }"</span> <span class="hljs-attr">autocomplete</span>=<span class="hljs-string">"id"</span> [<span class="hljs-attr">placeholder</span>]=<span class="hljs-string">"'Auto Assigned Id' | translate"</span> <span class="hljs-attr">readonly</span> /></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">translate</span>></span>Id<span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"><<span class="hljs-name">small</span> [<span class="hljs-attr">hidden</span>]=<span class="hljs-string">"f.id.valid || f.id.untouched"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span> <span class="hljs-attr">translate</span> ></span> Id is required <span class="hljs-tag"></<span class="hljs-name">small</span>></span> <span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"positionNumber"</span>></span>Position Number<span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"d-block mb-3"</span>></span> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"positionNumber"</span> [<span class="hljs-attr">ngClass</span>]=<span class="hljs-string">"{ 'is-invalid': f.positionNumber.errors }"</span> <span class="hljs-attr">autocomplete</span>=<span class="hljs-string">"positionNumber"</span> [<span class="hljs-attr">placeholder</span>]=<span class="hljs-string">"'Enter position number here' | translate"</span> <span class="hljs-attr">required</span> /></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">translate</span>></span>PositionNumber<span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"><<span class="hljs-name">small</span> [<span class="hljs-attr">hidden</span>]=<span class="hljs-string">"f.positionNumber.valid || f.positionNumber.untouched"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span> <span class="hljs-attr">translate</span> ></span> Position Number is required <span class="hljs-tag"></<span class="hljs-name">small</span>></span> <span class="hljs-tag"></<span class="hljs-name">label</span>></span>
<span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"positionTitle"</span>></span>Position Title<span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"d-block mb-3"</span>></span> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"positionTitle"</span> [<span class="hljs-attr">ngClass</span>]=<span class="hljs-string">"{ 'is-invalid': f.positionTitle.errors }"</span> <span class="hljs-attr">autocomplete</span>=<span class="hljs-string">"current-positionTitle"</span> [<span class="hljs-attr">placeholder</span>]=<span class="hljs-string">"'Enter title here' | translate"</span> <span class="hljs-attr">required</span> /></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">translate</span>></span>PositionTitle<span class="hljs-tag"></<

Options

span class="hljs-name">span</span>></span> <span class="hljs-tag"><<span class="hljs-name">small</span> [<span class="hljs-attr">hidden</span>]=<span class="hljs-string">"f.positionTitle.valid || f.positionTitle.untouched"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span> <span class="hljs-attr">translate</span> ></span> Position Title is required <span class="hljs-tag"></<span class="hljs-name">small</span>></span> <span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"positionDescription"</span>></span>Position Description<span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"d-block mb-3"</span>></span> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"positionDescription"</span> [<span class="hljs-attr">ngClass</span>]=<span class="hljs-string">"{ 'is-invalid': f.positionDescription.errors }"</span> <span class="hljs-attr">autocomplete</span>=<span class="hljs-string">"current-positionDescription"</span> [<span class="hljs-attr">placeholder</span>]=<span class="hljs-string">"'Enter description here' | translate"</span> <span class="hljs-attr">required</span> /></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">translate</span>></span>PositionDescription<span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"><<span class="hljs-name">small</span> [<span class="hljs-attr">hidden</span>]=<span class="hljs-string">" f.positionDescription.valid || f.positionDescription.untouched "</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span> <span class="hljs-attr">translate</span> ></span> Position Description is required <span class="hljs-tag"></<span class="hljs-name">small</span>></span> <span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"positionSalary"</span>></span>Position Salary<span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"d-block mb-3"</span>></span> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"positionSalary"</span> [<span class="hljs-attr">ngClass</span>]=<span class="hljs-string">"{ 'is-invalid': f.positionSalary.errors }"</span> <span class="hljs-attr">autocomplete</span>=<span class="hljs-string">"current-positionSalary"</span> [<span class="hljs-attr">placeholder</span>]=<span class="hljs-string">"'Enter salary here' | translate"</span> <span class="hljs-attr">required</span> /></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">translate</span>></span>PositionSalary<span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"><<span class="hljs-name">small</span> [<span class="hljs-attr">hidden</span>]=<span class="hljs-string">"f.positionSalary.valid || f.positionSalary.untouched"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span> <span class="hljs-attr">translate</span> ></span> Position Salary is required and must be numeric <span class="hljs-tag"></<span class="hljs-name">small</span>></span> <span class="hljs-tag"></<span class="hljs-name">label</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-footer"</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"float-left"</span>></span> <span class="hljs-comment"><!-- HTML markup for Create button --></span> <span class="hljs-tag"><<span class="hljs-name">button</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"onCreate()"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary w-20"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> [<span class="hljs-attr">disabled</span>]=<span class="hljs-string">"entryForm.invalid || !isAddNew"</span> *<span class="hljs-attr">ngIf</span>=<span class="hljs-string">"this.isAddNew"</span> ></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">translate</span>></span><span class="hljs-tag"><<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-plus"</span>></span><span class="hljs-tag"></<span class="hljs-name">i</span>></span> Save<span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"></<span class="hljs-name">button</span>></span> <span class="hljs-comment"><!-- HTML markup for Update button --></span> <span class="hljs-tag"><<span class="hljs-name">button</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"onUpdate()"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary w-20"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> [<span class="hljs-attr">disabled</span>]=<span class="hljs-string">"entryForm.invalid || isAddNew"</span> *<span class="hljs-attr">ngIf</span>=<span class="hljs-string">"!this.isAddNew"</span> ></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">translate</span>></span><span class="hljs-tag"><<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-edit"</span>></span><span class="hljs-tag"></<span class="hljs-name">i</span>></span> Update<span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"></<span class="hljs-name">button</span>></span> <span class="hljs-comment"><!-- HTML markup for Delete button --></span> <span class="hljs-tag"><<span class="hljs-name">button</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"onDelete()"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-danger w-20"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> [<span class="hljs-attr">disabled</span>]=<span class="hljs-string">"entryForm.invalid || isAddNew"</span> *<span class="hljs-attr">ngIf</span>=<span class="hljs-string">"!this.isAddNew"</span> ></span> <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">translate</span>></span><span class="hljs-tag"><<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-trash-alt"</span>></span><span class="hljs-tag"></<span class="hljs-name">i</span>></span> Delete<span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"></<span class="hljs-name">button</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"></<span class="hljs-name">form</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span></pre></div><p id="1f32">The above code represents an HTML form markup with a reactive form implementation in Angular. Let’s walk through the code section by section:</p><ol><li>The outermost container <code><div class="container-fluid"></code> sets the layout to occupy the full width of its parent container.</li><li>Inside the container, there is a <code><form></code> element with <code>[formGroup]="entryForm"</code> binding the form group to the <code>entryForm</code> property of the component.</li><li>Within the form, there is a <code><div class="card"></code> element representing a card-like container for the form content.</li><li>The <code><div class="card-header"></code> section contains the header of the card. It includes a heading <code><h3 class="text-secondary">Position</h3></code> and a <code><div></code> element with a link for navigation.</li><li>The <code><div class="card-body"></code> section contains the main content of the form. It starts with an <code><div class="alert alert-danger"></code> element that is conditionally hidden based on the <code>error</code> property. Inside the alert, there is a message indicating incorrect position details.</li><li>Inside the <code><div class="form-group"></code>, there are several form controls defined using <code><label></code> and <code><input></code> elements. Each input element has attributes like <code>formControlName</code> binding it to a specific form control in the <code>entryForm</code> FormGroup.</li><li>Each input element also has additional attributes like <code>[ngClass]</code> for dynamic styling based on the form control's validity, <code>autocomplete</code> for specifying the type of autocomplete, <code>[placeholder]</code> for placeholder text, and <code>required</code> for making the field mandatory.</li><li>Below each input, there is a <code><small></code> element that displays a validation error message when the form control is invalid or untouched. The <code>[hidden]</code> attribute is used to show or hide the error message based on the form control's state.</li><li>The <code><div class="card-footer"></code> section contains the footer of the card. It includes a <code><div class="float-left"></code> element with buttons for creating, updating, and deleting entries. The buttons have click event bindings to corresponding methods in the component and are conditionally shown or hidden based on the <code>isAddNew</code> property.</li></ol><p id="a37c">This code provides a basic implementation of an Angular reactive form with form controls and validation.</p><h1 id="21b2">Frequently Asked Questions</h1><h2 id="9867">Question 1: How to validate money in Angular?</h2><p id="3f23">Answer: Define the money input field in your form using the <code>FormControl</code> class. You can specify validators to enforce specific rules. For example, you can use the <code>Validators.pattern</code> validator with a regular expression <code>/^\d+(.\d{1,2})?/</code> to validate the money format. See below for an example code</p><div id="8a2e"><pre> <span class="hljs-title function_">ngOnInit</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">moneyForm</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormGroup</span>({ <span class="hljs-attr">money</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormControl</span>(<span class="hljs-string">''</span>, [ <span class="hljs-title class_">Validators</span>.<span class="hljs-property">required</span>, <span class="hljs-title class_">Validators</span>.<span class="hljs-title function_">pattern</span>(<span class="hljs-regexp">/^\d+(\.\d{1,2})?/</span>) ]) }); }</pre></div><h2 id="a315">Question 2: How to interact with native HTML form validation</h2><p id="0a9b">Answer: By default, Angular disables native HTML form validation by adding the <code>novalidate</code> attribute on the enclosing <code><form></code> element and uses directives to match these attributes with validator functions in the framework. This means that Angular handles the validation logic internally. However, if you want to use native validation in combination with Angular-based validation, you can add the <code>ngNativeValidate</code> attribute to the <code><form></code> element:</p><div id="1597"><pre><<span class="hljs-selector-tag">form</span> ngNativeValidate> ... </<span class="hljs-selector-tag">form</span>></pre></div><h1 id="4dc7">Recommended Contents</h1><ol><li><a href="https://readmedium.com/what-are-template-and-reactive-forms-in-angular-68cd285b38d5">What are Template and Reactive forms in Angular?</a></li><li><a href="https://readmedium.com/upgrading-angular-v15-to-v16-a-full-stack-web-development-project-using-angular-and-netcore-38aa7be95819">Upgrading Angular 15 to 16: A Full-stack Web Development Project using Angular and .NetCore WebAPI</a></li><li><a href="https://readmedium.com/load-angular-component-dynamically-in-bootstrap-modal-tutorial-8fcd65bcf074">Load Angular Component into Bootstrap Modal | Tutorial</a></li><li><a href="https://readmedium.com/load-bootstrap-5-toaster-in-angular-15-or-16-tutorial-b0996491e1a2">Implement Toast with Bootstrap 5 in Angular 15 or 16 | Tutorial</a></li><li><a href="https://readmedium.com/full-stack-angular-15-bootstrap-5-net-7-api-563227ef7a20">Fullstack Angular 15, Bootstrap 5 & NET 7 API: Project Demo and Tutorial</a></li><li><a href="https://readmedium.com/seven-object-oriented-programming-jokes-101b4c53a8a6">Seven Object-Oriented Programming Jokes</a></li></ol><h1 id="94c4">Summary</h1><figure id="db7b"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*yiwtNFr6XIwRJxKk"><figcaption>Photo by <a href="https://unsplash.com/@davidhofmann?utm_source=medium&amp;utm_medium=referral">David Hofmann</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p id="e1b1">Angular Reactive Forms is a powerful feature provided by the Angular framework for handling form-based data entry and validation in a reactive and declarative manner. It offers a flexible and robust approach to building forms that can be used for tasks such as data input, editing, and validation.</p><p id="c123"><i>Thanks for reading! Hope you found it useful. Want more? Please follow me and <a href="https://medium.com/@fuji-nguyen/membership">become a member</a> on medium for more articles. With your support, I’ll keep creating awesome content for you. Have a great day ahead! — Fuji Nguyen</i></p></article></body>

Angular 16 Reactive Form and Validation | Tutorial

Introduction

Angular Reactive Forms is a powerful feature of the Angular framework that allows you to create and manage forms in a reactive and declarative manner. Reactive forms provide a way to build complex, dynamic forms with ease.

Angular form validation is a feature provided by the Angular framework that allows you to validate user input in HTML forms. It enables you to define rules and conditions for input fields and provides feedback to users when their input is invalid.

How to Implement Reactive Form

To implement a reactive form in Angular, you’ll need to follow these steps:

Step 1: Import the required Angular modules In your component’s module file (e.g., app.module.ts), import the ReactiveFormsModule from @angular/forms. This module provides the necessary classes and directives for reactive forms.

Step 2: Create the form controls and form group In your component file (e.g., position.component.ts), import the necessary classes from @angular/forms, such as FormBuilder, FormGroup, and Validators. Use the FormBuilder to create the form controls and group them together in a form group.

Step 3: Bind the form controls in the template In your component’s template (e.g., position.component.html), use the formGroup directive to bind the form group to the HTML form element. For each form control, use the formControlName directive to bind it to the corresponding control in the form group.

Step 4: Handle form submission and validation In your component’s class, you can handle the form submission by implementing a method (e.g., submitForm()). In this method, you can check the validity of the form using the valid property of the form group. You can also access the form values using the value property.

Use Case

You are tasked with developing a data entry form to add/update the position table. The form is used to support adding or editing records. The validation includes the required data field and data type validation. Please refer to the figure below for an example screenshot of the form.

Form to add new record
Form to edit existing record

Source Code Discussed in This Blog Post

The source code discussed in this blog post is from the demo Talent Management project, which consists of three distinct but interrelated repositories. These repositories include

1. Angular 16 Client

The Angular 16 repository serves as the front-end interface for the project, providing users with a user-friendly and intuitive interface for managing and accessing talent data. The source code in this repository was first generated using the template ngX-Rocket before adding custom code.

2. Net 7 Api Resources

The NET7 API resource repository acts as the back-end of the system, providing access to talent data and implementing all necessary business logic. The source code of the API resource project was generated using the clean architecture Template OnionAPI which is available for download from Visual Studio Marketplace.

3. Duende IdentityServer for Token Service

The Dudende token service repository is responsible for managing authentication and authorization, ensuring that only authorized users can access the system’s sensitive talent data. The source code for this repository was generated using the template skoruba Admin UI.

You have the option to download the complete full-stack source code and run it on your localhost environment. When prompt for account to login, use (admin, Pa$$word123). For more information about this sample project, please visit Fullstack Angular 15, Bootstrap 5 & .NET 7 API: Project Demo and Tutorial.

Part 1: Reactive Form Typescript Walkthrough

Below is the Typescript code to bind HTML markup (see Part 2 later for HTML code) to a component class and implement the necessary methods for handling form submission and other logic taken from the source code file position.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { FormGroup, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { Logger } from '@app/core';
import { ApiHttpService } from '@app/services/api/api-http.service';
import { ApiEndpointsService } from '@app/services/api/api-endpoints.service';
import { Position } from '@shared/interfaces/position';
import { DataResponsePosition } from '@shared/interfaces/data-response-position';
import { ModalService } from '@app/services/modal/modal.service';

import { RxwebValidators, RxReactiveFormsModule } from '@rxweb/reactive-form-validators';
import { ToastService } from '@app/services/toast/toast.service';
import { NgIf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';

const log = new Logger('Detail');

@Component({
  selector: 'app-detail',
  templateUrl: './position-detail.component.html',
  styleUrls: ['./position-detail.component.scss'],
  standalone: true,
  imports: [ReactiveFormsModule, RxReactiveFormsModule, CommonModule, RouterLink, TranslateModule, NgIf],
})
export class PositionDetailComponent implements OnInit {
  formMode = 'New';
  sub: any;
  id: any;
  entryForm!: FormGroup;
  error: string | undefined;
  position!: Position;
  isAddNew: boolean = false;
  submitted = false;

  constructor(
    private toastService: ToastService,
    private route: ActivatedRoute,
    private formBuilder: FormBuilder,
    private apiHttpService: ApiHttpService,
    private apiEndpointsService: ApiEndpointsService,
    private modalService: ModalService
  ) {
    this.createForm();
  }

  ngOnInit() {
    this.sub = this.route.params.subscribe((params) => {
      this.id = params['id'];
      if (this.id !== undefined) {
        this.read(this.route.snapshot.paramMap.get('id'));
        this.formMode = 'Edit';
      } else {
        this.isAddNew = true;
        this.formMode = 'New';
      }
    });
    log.debug('ngOnInit:', this.id);
  }

  // Handle Create button click
  onCreate() {
    this.create(this.entryForm.value);
    log.debug('OnInsert: ', this.entryForm.value);
    log.debug('OnInsert: ', this.entryForm.get('positionNumber')!.value);
  }

  // Handle Update button click
  onUpdate() {
    this.put(this.entryForm.get('id')!.value, this.entryForm.value);
    this.showToaster('Great job!', 'Data is updated');
  }

  // Handle Delete button click
  onDelete() {
    this.modalService
      .OpenConfirmDialog('Position deletion', 'Are you sure you want to delete?')
      .then((Yes) => {
        if (Yes) {
          this.delete(this.entryForm.get('id')!.value);
          log.debug('onDelete: ', this.entryForm.value);
        }
      })
      .catch(() => {
        log.debug('onDelete: ', 'Cancel');
      });
  }
  // CRUD > Read, map to REST/HTTP GET
  read(id: any): void {
    this.apiHttpService.get(this.apiEndpointsService.getPositionByIdEndpoint(id), id).subscribe(
      //Assign resp to class-level model object.
      (resp: DataResponsePosition) => {
        //Assign data to class-level model object.
        this.position = resp.data;
        //Populate reactive form controls with model object properties.
        this.entryForm.setValue({
          id: this.position.id,
          positionNumber: this.position.positionNumber,
          positionTitle: this.position.positionTitle,
          positionDescription: this.position.positionDescription,
          positionSalary: this.position.positionSalary,
        });
      },
      (error) => {
        log.debug(error);
      }
    );
  }
  // CRUD > Delete, map to REST/HTTP DELETE
  delete(id: any): void {
    this.apiHttpService.delete(this.apiEndpointsService.deletePositionByIdEndpoint(id), id).subscribe(
      (resp: any) => {
        log.debug(resp);
        this.showToaster('Great job!', 'Data is deleted');
        this.entryForm.reset();
        this.isAddNew = true;
      },
      (error) => {
        log.debug(error);
      }
    );
  }

  // CRUD > Create, map to REST/HTTP POST
  create(data: any): void {
    this.apiHttpService.post(this.apiEndpointsService.postPositionsEndpoint(), data).subscribe((resp: any) => {
      this.id = resp.data; //guid return in data
      this.showToaster('Great job!', 'Data is inserted');
      this.entryForm.reset();
    });
  }

  // CRUD > Update, map to REST/HTTP PUT
  put(id: string, data: any): void {
    this.apiHttpService.put(this.apiEndpointsService.putPositionsPagedEndpoint(id), data).subscribe((resp: any) => {
      this.id = resp.data; //guid return in data
    });
  }

  // reactive form
  private createForm() {
    this.entryForm = this.formBuilder.group({
      id: [''],
      positionNumber: ['', Validators.required],
      positionTitle: ['', Validators.required],
      positionDescription: ['', Validators.required],
      positionSalary: ['', RxwebValidators.numeric({ allowDecimal: true, isFormat: false })],
    });
  }

  // call modal service
  showToaster(title: string, message: string) {
    this.toastService.show(title, message, {
      classname: 'bg-success text-light',
      delay: 2000,
      autohide: true,
    });
  }

    // convenience getter for easy access to form fields
    get f() { return this.entryForm.controls; }
}

The above code is an Angular component called PositionDetailComponent. It represents the TypeScript implementation of the component and includes various imports and class properties and methods. Let's go through the code section by section:

  1. The imports at the top of the code include necessary dependencies from Angular and external libraries. These imports are used for various functionalities such as routing, form handling, validation, services, and more.
  2. The component decorator is used to specify the component’s selector, template URL, and style URLs. It also includes the standalone: true property, which indicates that this component can be used independently without any parent component.
  3. Inside the component class definition, there are various class properties declared. Some notable properties include formMode for tracking the form mode (New or Edit), sub for storing the subscription to route params, id for storing the ID of the position, entryForm for the reactive form group, error for storing error messages, position for storing position data, isAddNew for determining if it's a new position, and submitted for tracking form submission.
  4. The constructor initializes the form by calling the createForm() method and injects the required services like ToastService, ActivatedRoute, FormBuilder, ApiHttpService, ApiEndpointsService, and ModalService.
  5. The ngOnInit() method is called when the component is initialized. It subscribes to the route params and retrieves the id parameter. Based on the existence of the id, it sets the form mode and calls the read() method to fetch the position data if in edit mode.
  6. The component includes methods for handling button clicks and CRUD operations. onCreate() handles the create button click and calls the create() method to submit the form data. onUpdate() handles the update button click and calls the put() method to update the position data. onDelete() handles the delete button click and prompts the user for confirmation using the ModalService. If confirmed, it calls the delete() method to delete the position data.
  7. The read(), delete(), create(), and put() methods make HTTP requests using the ApiHttpService to perform CRUD operations on the position data. They handle the response and error accordingly.
  8. The createForm() method is used to create the reactive form using the formBuilder. It defines form controls with initial values and validators.
  9. The showToaster() method is used to display a toast message using the ToastService. It shows a success message with a specific title and message.
  10. The get f() method is a getter property that provides easy access to the form fields in the template using entryForm.controls.

Overall, this component manages the creation, reading, updating, and deleting (CRUD) operations for a position entity using a reactive form. It communicates with an API through the ApiHttpService and displays toast messages for success or failure using the ToastService.

If you are new to the Reactive Form, pay special attention to the block of code below

private createForm() {
    this.entryForm = this.formBuilder.group({
      id: [''],
      positionNumber: ['', Validators.required],
      positionTitle: ['', Validators.required],
      positionDescription: ['', Validators.required],
      positionSalary: ['', RxwebValidators.numeric({ allowDecimal: true, isFormat: false })],
    });
  }

The method createForm() creates a form using the formBuilder service from Angular's Reactive Forms module. Let's break down the code step by step:

  1. The method is declared as private, which means it can only be accessed within the same class.
  2. Inside the method, a form is created using this.formBuilder.group(). The formBuilder is an instance of FormBuilder class provided by Angular's Reactive Forms module.
  3. The form group is initialized with an object literal that defines the form controls. Each form control is represented by a key-value pair, where the key is the name of the control and the value is an array with two elements.
  4. The first element of each control’s array is the initial value of the control. In this case, the id control is initialized with an empty string ''.
  5. The second element of each control’s array is an array of validators to apply to the control. Validators are functions used to validate user input. In this code, the Validators.required validator is used to make the positionNumber, positionTitle, and positionDescription controls required.
  6. The positionSalary control is configured with a custom validator called RxwebValidators.numeric(). This validator is from the RxwebValidators library, which is used to validate numeric inputs. It allows decimal numbers (allowDecimal: true) and does not enforce a specific format (isFormat: false).
  7. After creating the form group, it is assigned to the entryForm property of the class. This property is assumed to be declared somewhere in the class.

Overall, this method sets up a form with five controls: id, positionNumber, positionTitle, positionDescription, and positionSalary, with appropriate validators applied to some of the controls. The form can be used for data entry and validation in an Angular application.

Part 2: Reactive Form HTML Markup Walkthrough

Below is an example of an Angular reactive form that includes multiple form controls with validation taken from source code file position.component.html

<div class="container-fluid">
  <!-- HTML form mark up -->
  <form [formGroup]="entryForm" novalidate>
    <div class="card">
      <div class="card-header">
        <div class="float-start"><h3 class="text-secondary">Position</h3></div>
        <div class="float-end">
          <!-- HTML markup for form mode New or Edit -->
          <a class="btn text-dark" [routerLink]="['/position']"><i class="fa fa-arrow-left"></i> Back</a>
        </div>
      </div>
      <div class="card-body">
        <div class="alert alert-danger" [hidden]="!error" translate>
          Position Number, Title, Description or Salary incorrect.
        </div>
        <div class="form-group">
          <label for="id">Id</label>
          <label class="d-block mb-3">
            <input
              type="text"
              class="form-control"
              formControlName="id"
              [ngClass]="{ 'is-invalid': f.id.errors }"
              autocomplete="id"
              [placeholder]="'Auto Assigned Id' | translate"
              readonly
            />
            <span hidden translate>Id</span>
            <small
              [hidden]="f.id.valid || f.id.untouched"
              class="text-danger"
              translate
            >
              Id is required
            </small>
          </label>
          <label for="positionNumber">Position Number</label>
          <label class="d-block mb-3">
            <input
              type="text"
              class="form-control"
              formControlName="positionNumber"
              [ngClass]="{ 'is-invalid': f.positionNumber.errors }"
              autocomplete="positionNumber"
              [placeholder]="'Enter position number here' | translate"
              required
            />
            <span hidden translate>PositionNumber</span>
            <small
              [hidden]="f.positionNumber.valid || f.positionNumber.untouched"
              class="text-danger"
              translate
            >
              Position Number is required
            </small>
          </label>          
          <label for="positionTitle">Position Title</label>          <label class="d-block mb-3">
            <input
              type="text"
              class="form-control"
              formControlName="positionTitle"
              [ngClass]="{ 'is-invalid': f.positionTitle.errors }"
              autocomplete="current-positionTitle"
              [placeholder]="'Enter title here' | translate"
              required
            />
            <span hidden translate>PositionTitle</span>
            <small
              [hidden]="f.positionTitle.valid || f.positionTitle.untouched"
              class="text-danger"
              translate
            >
              Position Title is required
            </small>
          </label>
          <label for="positionDescription">Position Description</label>          <label class="d-block mb-3">
            <input
              type="text"
              class="form-control"
              formControlName="positionDescription"
              [ngClass]="{ 'is-invalid': f.positionDescription.errors }"
              autocomplete="current-positionDescription"
              [placeholder]="'Enter description here' | translate"
              required
            />
            <span hidden translate>PositionDescription</span>
            <small
              [hidden]="
                f.positionDescription.valid || f.positionDescription.untouched
              "
              class="text-danger"
              translate
            >
              Position Description is required
            </small>
          </label>
          <label for="positionSalary">Position Salary</label>
          <label class="d-block mb-3">
            <input
              type="text"
              class="form-control"
              formControlName="positionSalary"
              [ngClass]="{ 'is-invalid': f.positionSalary.errors }"
              autocomplete="current-positionSalary"
              [placeholder]="'Enter salary here' | translate"
              required
            />
            <span hidden translate>PositionSalary</span>
            <small
              [hidden]="f.positionSalary.valid || f.positionSalary.untouched"
              class="text-danger"
              translate
            >
              Position Salary is required and must be numeric
            </small>
          </label>
        </div>
      </div>
      <div class="card-footer">
        <div class="float-left">
          <!-- HTML markup for Create button -->
          <button
            (click)="onCreate()"
            class="btn btn-primary w-20"
            type="submit"
            [disabled]="entryForm.invalid || !isAddNew"
            *ngIf="this.isAddNew"
          >
            <span translate><i class="fas fa-plus"></i> Save</span>
          </button>
          <!-- HTML markup for Update button -->
          <button
            (click)="onUpdate()"
            class="btn btn-primary w-20"
            type="submit"
            [disabled]="entryForm.invalid || isAddNew"
            *ngIf="!this.isAddNew"
          >
            <span translate><i class="fas fa-edit"></i> Update</span>
          </button>
          <!-- HTML markup for Delete button -->
          <button
            (click)="onDelete()"
            class="btn btn-danger w-20"
            type="submit"
            [disabled]="entryForm.invalid || isAddNew"
            *ngIf="!this.isAddNew"
          >
            <span translate><i class="fas fa-trash-alt"></i> Delete</span>
          </button>
        </div>
      </div>
    </div>
  </form>
</div>

The above code represents an HTML form markup with a reactive form implementation in Angular. Let’s walk through the code section by section:

  1. The outermost container <div class="container-fluid"> sets the layout to occupy the full width of its parent container.
  2. Inside the container, there is a <form> element with [formGroup]="entryForm" binding the form group to the entryForm property of the component.
  3. Within the form, there is a <div class="card"> element representing a card-like container for the form content.
  4. The <div class="card-header"> section contains the header of the card. It includes a heading <h3 class="text-secondary">Position</h3> and a <div> element with a link for navigation.
  5. The <div class="card-body"> section contains the main content of the form. It starts with an <div class="alert alert-danger"> element that is conditionally hidden based on the error property. Inside the alert, there is a message indicating incorrect position details.
  6. Inside the <div class="form-group">, there are several form controls defined using <label> and <input> elements. Each input element has attributes like formControlName binding it to a specific form control in the entryForm FormGroup.
  7. Each input element also has additional attributes like [ngClass] for dynamic styling based on the form control's validity, autocomplete for specifying the type of autocomplete, [placeholder] for placeholder text, and required for making the field mandatory.
  8. Below each input, there is a <small> element that displays a validation error message when the form control is invalid or untouched. The [hidden] attribute is used to show or hide the error message based on the form control's state.
  9. The <div class="card-footer"> section contains the footer of the card. It includes a <div class="float-left"> element with buttons for creating, updating, and deleting entries. The buttons have click event bindings to corresponding methods in the component and are conditionally shown or hidden based on the isAddNew property.

This code provides a basic implementation of an Angular reactive form with form controls and validation.

Frequently Asked Questions

Question 1: How to validate money in Angular?

Answer: Define the money input field in your form using the FormControl class. You can specify validators to enforce specific rules. For example, you can use the Validators.pattern validator with a regular expression /^\d+(\.\d{1,2})?$/ to validate the money format. See below for an example code

  ngOnInit() {
    this.moneyForm = new FormGroup({
      money: new FormControl('', [
        Validators.required,
        Validators.pattern(/^\d+(\.\d{1,2})?$/)
      ])
    });
  }

Question 2: How to interact with native HTML form validation

Answer: By default, Angular disables native HTML form validation by adding the novalidate attribute on the enclosing <form> element and uses directives to match these attributes with validator functions in the framework. This means that Angular handles the validation logic internally. However, if you want to use native validation in combination with Angular-based validation, you can add the ngNativeValidate attribute to the <form> element:

<form ngNativeValidate>
  ...
</form>

Recommended Contents

  1. What are Template and Reactive forms in Angular?
  2. Upgrading Angular 15 to 16: A Full-stack Web Development Project using Angular and .NetCore WebAPI
  3. Load Angular Component into Bootstrap Modal | Tutorial
  4. Implement Toast with Bootstrap 5 in Angular 15 or 16 | Tutorial
  5. Fullstack Angular 15, Bootstrap 5 & NET 7 API: Project Demo and Tutorial
  6. Seven Object-Oriented Programming Jokes

Summary

Photo by David Hofmann on Unsplash

Angular Reactive Forms is a powerful feature provided by the Angular framework for handling form-based data entry and validation in a reactive and declarative manner. It offers a flexible and robust approach to building forms that can be used for tasks such as data input, editing, and validation.

Thanks for reading! Hope you found it useful. Want more? Please follow me and become a member on medium for more articles. With your support, I’ll keep creating awesome content for you. Have a great day ahead! — Fuji Nguyen

Angular
Reactive Forms
Reactive Form Validation
Recommended from ReadMedium