Angular provides two approaches for defining our views; i.e. ,Template-Driven Forms & Reactive Forms. Template driven forms give you more control over designing your HTML, but at the same time it has a drawback in that view is heavily loaded with HTML Content. In contrast, Reactive Forms provides you with an approach to define your HTML Form with all its validation logic inside your Component making your views (HTML) a lot more readable. Since your view creation logic is inside Component you can actually do programming on it when required.
In this article we’ll try to create a use case wherein, we’ll allow Admins of the portal to create new employee records in the System & when we click on the Save it will be actually saved to the Database. For adding a new empty row to the Table I’ve provided a AddRow button which adds an empty Row to the PrimeNg Turbo Table, with an option of deleting any row. To create the sample project I've used Angular 9 Reactive forms & PrimeNg turbo table. So let’s start with coding our sample project. I’ve installed the pre-requisite libraries such as PrimeNg, FontAwesome & Bootstrap in my project & added the reference in angular.json file. Here is the code snippet for the same.
angular.json
- "styles": [
- "node_modules/bootstrap/dist/css/bootstrap.min.css",
- "node_modules/font-awesome/css/font-awesome.min.css",
- "node_modules/primeng/resources/themes/rhea/theme.css",
- "node_modules/primeng/resources/primeng.min.css",
- "src/styles.css"
- ]
I’ve added a new component, add-employee, to our application using the angular/cli & referred it in our app.component HTML file using the add-employee component selector.
Here is the code snippet for our add-employee component ts file. We started off by defining our FromGroup for the HTML Page inside add-employee.component.ts file:
- employeeForm: FormGroup;
- columns:string[];
Inside the constructor I’ve initialized the columns array with some 4 columns i..e:
- this.columns = ["Name", "Address", "Salary", "IsActive", "Delete"];
Our reactive form HTML creation logic comes inside the ngOnInit method wherein we called the createForm() method which actually does the work of initializing the employeeForm & adding a blank empty row to the Table as a FormGroup. Now using ReactiveForms we are trying to build a table, which basically will have one or more rows, so for defining the tableRows I’d used FormGroupArray. Here is the code snippet performing the same.
-
-
-
- private createForm(): void {
- this.employeeForm = this.formBuilder.group({
-
- tableRowArray: this.formBuilder.array([
- this.createTableRow()
- ])
- })
- }
Here is the code snippet for CreateTableRow() which provides a controls for each table row cell, as we only have 4 columns; i.e., Name, Address, Salary & IsActive. Below method creates this using the FormControl class.
-
-
-
- private createTableRow(): FormGroup {
- return this.formBuilder.group({
- name: new FormControl(null, {
- validators: [Validators.required, Validators.minLength(3), Validators.maxLength(50)]
- }),
- address: new FormControl(null, {
- validators: [Validators.required, Validators.maxLength(500)]
- }),
- salary: new FormControl(null, {
- validators: [Validators.required, Validators.pattern(/^\d{1,6}(?:\.\d{0,2})?$/), Validators.minLength(3), Validators.maxLength(50)]
- }),
- isActive: new FormControl({
- value: true,
- disabled: true
- })
- });
- }
NOTE
You can explicitly enable or disable a control like we did for isActive column case, provide a default value of true to it & also disabled the control.
To get the FormGroupArray I’ve defined a getter method which returns the FormArray from the tableRowArray.
- get tableRowArray(): FormArray {
- return this.employeeForm.get('tableRowArray') as FormArray;
- }
For adding new empty row to the table I’ve created a method which adds a new FormGroup by using createNewRow() in tableRowArray FormArray.
- addNewRow(): void {
- this.tableRowArray.push(this.createTableRow());
- }
Similarly for deleting the existing row I’ve defined OnDeleteRow method which take Rowindex & accordingly removes that row from our tableRowArray FormArray.
- onDeleteRow(rowIndex:number): void {
- this.tableRowArray.removeAt(rowIndex);
- }
Now let’s move our attention towards our HTML add-employee.component.html. I’ve defined our HTML form using the form tag, remember the employeeForm which we created in our TS file.
- <form [formGroup]="employeeForm">
Inside this form tag we defined our PrimeNg Turbo Table using the p-table tag.
- <p-table [value]="tableRowArray.controls" [responsive]="true">
- <ng-template pTemplate="header">
- <tr>
- <ng-container *ngFor="let col of columns">
- <td>{{col}}</td>
- </ng-container>
- </tr>
- </ng-template>
Now let’s define the template for our Body wherein we’ll actually use the tableRowArray FormArray we created in our TS file.
- <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
- <ng-container formArrayName="tableRowArray">
- <tr [formGroupName]="rowIndex">
- <td>
- <input type="text" class="form-control form-control-sm" formControlName="name" />
- <div class="text-danger" *ngIf="rowData.get('name').errors && (rowData.get('name').dirty || rowData.get('name').touched)">
- <div *ngIf="rowData.get('name').errors?.required">Name is Required</div>
- </div>
- </td>
- <td>
- <input type="text" class="form-control form-control-sm" formControlName="address" />
- <div class="text-danger" *ngIf="rowData.get('address').errors && (rowData.get('address').dirty || rowData.get('address').touched)">
- <div *ngIf="rowData.get('address').errors?.required">Address is Required</div>
- </div>
- </td>
- <td>
- <input type="text" class="form-control form-control-sm" formControlName="salary" />
- <div class="text-danger" *ngIf="rowData.get('salary').errors && (rowData.get('salary').dirty || rowData.get('salary').touched)">
- <div *ngIf="rowData.get('salary').errors?.required">Salary is Required</div>
- <div *ngIf="rowData.get('salary').errors?.pattern">Number Only, max 6 digits with max 2 decimals</div>
- </div>
- </td>
- <td>
- <div class="form-check">
- <input type="checkbox" class="form-check-input" formControlName="isActive" />
- <label class="form-check-label"></label>
- </div>
- </td>
- <td>
- <button type="button" class="btn btn-default" title="Delete" (click)="onDeleteRow(rowIndex)">
- <i class="fa fa-trash-o" aria-hidden="true"></i>
- </button>
- </td>
- </tr>
- </ng-container>
- </ng-template>
For allowing the user to add new blank rows to the table we provide the user with Add Row button, here is the code snippet for the same.
- <div class="card-footer">
- <button type="button" class="btn btn-primary btn-sm pull-left" (click)="addNewRow()">Add Row</button>
- <button type="button" class="btn btn-success btn-sm pull-right" title="Save">Save</button>
- </div>
Here is the full code snippet for the add-employee.component.html file
- <div class="card">
- <div class="card-header">
- <h5>Reactive Forms with PrimeNg Table</h5>
- </div>
- <div class="card-body">
- <form [formGroup]="employeeForm">
- <p-table [value]="tableRowArray.controls" [responsive]="true">
- <ng-template pTemplate="header">
- <tr>
- <ng-container *ngFor="let col of columns">
- <td>{{col}}</td>
- </ng-container>
- </tr>
- </ng-template>
- <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
- <ng-container formArrayName="tableRowArray">
- <tr [formGroupName]="rowIndex">
- <td>
- <input type="text" class="form-control form-control-sm" formControlName="name" />
- <div class="text-danger" *ngIf="rowData.get('name').errors && (rowData.get('name').dirty || rowData.get('name').touched)">
- <div *ngIf="rowData.get('name').errors?.required">Name is Required</div>
- </div>
- </td>
- <td>
- <input type="text" class="form-control form-control-sm" formControlName="address" />
- <div class="text-danger" *ngIf="rowData.get('address').errors && (rowData.get('address').dirty || rowData.get('address').touched)">
- <div *ngIf="rowData.get('address').errors?.required">Address is Required</div>
- </div>
- </td>
- <td>
- <input type="text" class="form-control form-control-sm" formControlName="salary" />
- <div class="text-danger" *ngIf="rowData.get('salary').errors && (rowData.get('salary').dirty || rowData.get('salary').touched)">
- <div *ngIf="rowData.get('salary').errors?.required">Salary is Required</div>
- <div *ngIf="rowData.get('salary').errors?.pattern">Number Only, max 6 digits with max 2 decimals</div>
- </div>
- </td>
- <td>
- <div class="form-check">
- <input type="checkbox" class="form-check-input" formControlName="isActive" />
- <label class="form-check-label"></label>
- </div>
- </td>
- <td>
- <button type="button" class="btn btn-default" title="Delete" (click)="onDeleteRow(rowIndex)">
- <i class="fa fa-trash-o" aria-hidden="true"></i>
- </button>
- </td>
- </tr>
- </ng-container>
- </ng-template>
- </p-table>
- </form>
- </div>
- <div class="card-footer">
- <button type="button" class="btn btn-primary btn-sm pull-left" (click)="addNewRow()">Add Row</button>
- <button type="button" class="btn btn-success btn-sm pull-right" title="Save">Save</button>
- </div>
- </div>
Now run the application & try playing with Adding a new blank row, deleting the row or with validations. Below is the snapshot for the same.