Form Validation Inside Angular Material Table (MatTable) In Angular 9

Recently while working on one of my projects there was a requirement for me to have an Excel-like editing feature on my page. Also it should be able to handle validations if user doesn't provide any required field data. The tech stack we'd used for building this application is Angular 9. We also had used Angular material for creating the table. So here is the simple approach we took. On page render we loaded the mat table with all the data we had, by providing appropriate user controls depending on field type. For instance, for fields such as name we would render Textbox control, but for control such as DOB we would render DatePicker control etc. For this simple demo we'll consider that all of the fields are required. However in the next article we'll try to make it more dynamic; i.e. fields information would come from an external configuration file. You can try to link it with scenarios like the field information is coming from some Sharepoint list. That's going to be the next article in the queue.
 
Here is the sample output for the current article we are trying to develop,
 
 
For this current article, we would use dummy JSON data, I've defined one EmployeeDataStore service which provides our JSON data. Here is our Employee model,
 
Employee.model.ts
  1. export class Employee {  
  2.     photo: string;  
  3.     name: string;  
  4.     email: string;  
  5.     address: string;  
  6.     dob: string;  
  7.     gender: string;  
  8. }  
Here is the code snippet for EmployeeDataStore service. In real applications this is going to  be the service which will invoke the actual rest api call.
 
EmployeeDataStore.service.ts
  1. import {  
  2.     Employee  
  3. } from '../models/employee.model';  
  4. import {  
  5.     Injectable  
  6. } from '@angular/core';  
  7. @Injectable()  
  8. export class EmployeeDataStoreService {  
  9.     private eData: Employee[] = [];  
  10.     public getEmployees() {  
  11.         const json = `[  
  12. {  
  13.    "photo""http://placehold.it/32x32",  
  14.    "age": 25,  
  15.    "name""Aimee Weeks",  
  16.    "gender""female",  
  17.    "email""aimeeweeks@codax.com",  
  18.    "dob""11/08/1988",  
  19.    "address""842 Java Street, Kapowsin, Mississippi, 8052"  
  20. },  
  21. {  
  22.    "photo""http://placehold.it/32x32",  
  23.    "age": 22,  
  24.    "name""Vicky Avery",  
  25.    "gender""female",  
  26.    "email""vickyavery@codax.com",  
  27.    "dob""08/11/1988",  
  28.    "address""803 Vanderveer Street, Remington, South Carolina, 1829"  
  29. },  
  30. {  
  31.    "photo""http://placehold.it/32x32",  
  32.    "age": 30,  
  33.    "name""Cleveland Vance",  
  34.    "gender""male",  
  35.    "email""clevelandvance@codax.com",  
  36.    "dob""21/12/1986",  
  37.    "address""397 Hamilton Walk, Loretto, Massachusetts, 1096"  
  38. },  
  39. {  
  40.    "photo""http://placehold.it/32x32",  
  41.    "age": 40,  
  42.    "name""Craft Frost",  
  43.    "gender""male",  
  44.    "email""craftfrost@codax.com",  
  45.    "dob""02/02/1970",  
  46.    "address""519 Arlington Place, Waukeenah, Delaware, 4549"  
  47. },  
  48. {  
  49.    "photo""http://placehold.it/32x32",  
  50.    "age": 23,  
  51.    "name""Debbie Blevins",  
  52.    "gender""female",  
  53.    "email""debbieblevins@codax.com",  
  54.    "dob""02/05/1980",  
  55.    "address""855 Hinckley Place, Edmund, Virginia, 6139"  
  56. },  
  57. {  
  58.    "photo""http://placehold.it/32x32",  
  59.    "age": 27,  
  60.    "name""Woodard Lott",  
  61.    "gender""male",  
  62.    "email""woodardlott@codax.com",  
  63.    "dob""30/01/1982",  
  64.    "address""865 Karweg Place, Johnsonburg, Utah, 4270"  
  65. }  
  66. ]`;  
  67.         this.eData = JSON.parse(json);  
  68.         return this.eData;  
  69.     }  
  70. }  
Here is our app.component.html file wherein we defined the UI of our application,
  1. <form #testForm="ngForm" (ngSubmit)="testForm.form.valid" novalidate>  
  2.     <mat-card>  
  3.         <mat-card-header>  
  4.             <mat-card-title>Angular 9 Template Driven Form</mat-card-title>  
  5.             <mat-card-subtitle>Mat Table Validation</mat-card-subtitle>  
  6.         </mat-card-header>  
  7.         <mat-card-content>  
  8.             <mat-table [dataSource]="elistMatTableDataSource">  
  9.                 <ng-container matColumnDef="name">  
  10.                     <mat-header-cell *matHeaderCellDef>  
  11. Name  
  12. </mat-header-cell>  
  13.                     <mat-cell *matCellDef="let employee; let rowIdx = index;">  
  14.                         <mat-form-field>  
  15.                             <mat-label></mat-label>  
  16.                             <input matInput #name="ngModel" name="txtName{{rowIdx}}" placeholder="Name" [(ngModel)]="employee.name"  
  17. required>  
  18.                                 <mat-error *ngIf="name?.invalid">  
  19.                                     <div *ngIf="name.errors.required">Name is required</div>  
  20.                                 </mat-error>  
  21.                             </mat-form-field>  
  22.                         </mat-cell>  
  23.                     </ng-container>  
  24.                     <ng-container matColumnDef="email">  
  25.                         <mat-header-cell *matHeaderCellDef>  
  26. Email  
  27. </mat-header-cell>  
  28.                         <mat-cell *matCellDef="let employee; let rowIdx = index;">  
  29.                             <mat-form-field>  
  30.                                 <mat-label></mat-label>  
  31.                                 <input matInput #email="ngModel" name="txtEmail{{rowIdx}}" placeholder="Email"  
  32. [(ngModel)]="employee.email" required>  
  33.                                     <mat-error *ngIf="email?.invalid">  
  34.                                         <div *ngIf="email.errors.required">Email is required</div>  
  35.                                     </mat-error>  
  36.                                 </mat-form-field>  
  37.                             </mat-cell>  
  38.                         </ng-container>  
  39.                         <ng-container matColumnDef="address">  
  40.                             <mat-header-cell *matHeaderCellDef>  
  41. Address  
  42. </mat-header-cell>  
  43.                             <mat-cell *matCellDef="let employee; let rowIdx = index;">  
  44.                                 <mat-form-field>  
  45.                                     <mat-label></mat-label>  
  46.                                     <input matInput #address="ngModel" name="txtAddress{{rowIdx}}" placeholder="Address"  
  47. [(ngModel)]="employee.address" required style="text-overflow: ellipsis;">  
  48.                                         <mat-error *ngIf="address?.invalid">  
  49.                                             <div *ngIf="address.errors.required">Address` is required</div>  
  50.                                         </mat-error>  
  51.                                     </mat-form-field>  
  52.                                 </mat-cell>  
  53.                             </ng-container>  
  54.                             <ng-container matColumnDef="dob">  
  55.                                 <mat-header-cell *matHeaderCellDef>  
  56. Dob  
  57. </mat-header-cell>  
  58.                                 <mat-cell *matCellDef="let employee; let rowIdx = index;">  
  59.                                     <mat-form-field>  
  60.                                         <mat-label></mat-label>  
  61.                                         <input matInput #dob="ngModel" name="txtDob{{rowIdx}}" placeholder="DOB"  
  62. [ngModel]="employee.dob | date:'yyyy-MM-dd'" (ngModelChange)="employee.dob = $event" [max]="todaysDate"  
  63. required [matDatepicker]="picker">  
  64.                                             <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>  
  65.                                             <mat-datepicker #picker></mat-datepicker>  
  66.                                             <mat-error *ngIf="dob?.invalid">  
  67.                                                 <div *ngIf="dob.errors.required">Date Of Birth is required</div>  
  68.                                             </mat-error>  
  69.                                         </mat-form-field>  
  70.                                     </mat-cell>  
  71.                                 </ng-container>  
  72.                                 <ng-container matColumnDef="gender">  
  73.                                     <mat-header-cell *matHeaderCellDef>  
  74. Gender  
  75. </mat-header-cell>  
  76.                                     <mat-cell *matCellDef="let employee; let rowIdx = index;">  
  77.                                         <mat-form-field>  
  78.                                             <mat-label></mat-label>  
  79.                                             <mat-select matInput #gender="ngModel" name="ddlGender{{rowIdx}}" placeholder="Gender"  
  80. [(ngModel)]="employee.gender" required>  
  81.                                                 <mat-option value="female">Female</mat-option>  
  82.                                                 <mat-option value="male">Male</mat-option>  
  83.                                                 <mat-option value="others">Others</mat-option>  
  84.                                             </mat-select>  
  85.                                             <mat-error *ngIf="gender?.invalid">  
  86.                                                 <div *ngIf="gender.errors.required">Gender is required</div>  
  87.                                             </mat-error>  
  88.                                         </mat-form-field>  
  89.                                     </mat-cell>  
  90.                                 </ng-container>  
  91.                                 <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>  
  92.                                 <mat-row *matRowDef="let row; columns: displayedColumns; let i = index;"></mat-row>  
  93.                             </mat-table>  
  94.                         </mat-card-content>  
  95.                         <mat-card-actions>  
  96.                             <button mat-raised-button color="primary" [disabled]="testForm.invalid" (click)="save(testForm);">SAVE</button>  
  97.                         </mat-card-actions>  
  98.                     </mat-card>  
  99.                 </form>  
Finally our app.component.ts file,
  1. import {  
  2.     Component,  
  3.     OnInit  
  4. } from '@angular/core';  
  5. import {  
  6.     MatTableDataSource  
  7. } from '@angular/material/table';  
  8. import {  
  9.     NgForm  
  10. } from '@angular/forms';  
  11. import {  
  12.     Employee  
  13. } from './models/employee.model';  
  14. import {  
  15.     EmployeeDataStoreService  
  16. } from './data-store/employee-data-store.service';  
  17. @Component({  
  18.     selector: 'app-root',  
  19.     templateUrl: './app.component.html',  
  20.     styleUrls: ['./app.component.css']  
  21. })  
  22. export class AppComponent implements OnInit {  
  23.     title = 'table-validation-demo';  
  24.     elistMatTableDataSource = new MatTableDataSource < Employee > ();  
  25.     displayedColumns: string[];  
  26.     todaysDate: Date;  
  27.     constructor(private eDataStore: EmployeeDataStoreService) {  
  28.         this.displayedColumns = ['name''address''email''dob''gender'];  
  29.         this.todaysDate = new Date();  
  30.     }  
  31.     ngOnInit() {  
  32.         this.elistMatTableDataSource.data = this.eDataStore.getEmployees();  
  33.     }  
  34.     save(form: NgForm): void {  
  35.         console.log('Form Status', form.valid);  
  36.         console.log('Form Touched', form.touched);  
  37.         console.log('Form Untouch', form.untouched);  
  38.         console.log('Form Invalid', form.invalid);  
  39.         console.log('Form Pristine', form.pristine);  
  40.         // do API operation  
  41.     }  
  42. }  
Finally just run the application & you can see our validations working.