Angular 10|9|8 Edit/ Add/ Delete Rows in Material Table with using Dialogs inline Row Operation

In this Angular tutorial, we are going to learn how to implement a Material table in Angular 10/9/8/7/6/5 application and perform inline CRUD operations using a dialog modal.

We’ll be creating a datatable grid with the Angular Material Table component which will have some basic operations like Add, Update and Delete rows using the Dialog component.

Angular Material Table component is full of features and a wide variety of options are available to create data-rich grids in a very optimized way. It provides a lot basic to advance functionalities like filter, sorting, pagination, expand-collapse, custom templating, etc.

But discussion of these features is out of the scope of this article, so let’s stick to some basic operations we are going to implement like Add, Delete and Update table rows.

To do these operations on table rows, we’ll use the Dialog component on Angular material. A dialog is a popup layout that is displayed to a user above all content in the center of the screen, which can easily catch user eyeballs. Dialog component can be used in many areas like getting prompt responses, showing alert messages, notifying users, getting other user inputs, etc.

To keep this simple we will have two fields in table i.e ID and Name with an extra Action column to have Edit and Delete control links.

So, let’s get to work…

 

Create a new Angular Project

We will start with a new Angular project, using Angular CLI tool is very easy and quick to start with a project.

# Install Angular CLI
$ npm install -g @angular/cli

# Create new project
$ ng new angular-material-table-inline-ops

# Enter project
$ cd angular-material-table-inline-ops

# Run project in VS Code
$ code .

 

Install Material Package

Run the following command to install the Material package

$ ng add @angular/material

Answer a few questions asked to do configuration in the Angular project

# ? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink
# ? Set up global Angular Material typography styles? Yes 
# ? Set up browser animations for Angular Material? Yes

That’s it … we are done with Angular Material installation and configuration.

 

Update App Module

We are going to use Table, Dialog, FormFields, Input, Buttons as Material components in our tutorial. So we need to import these in the app.module.ts file and also add in the imports array to use them in the application.

Also, import FormsModule as we will use [(ngModel)] in our Dialog.

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { FormsModule } from '@angular/forms';

import { MatTableModule } from '@angular/material/table';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    MatTableModule,
    MatDialogModule,
    MatFormFieldModule,
    MatInputModule,
    MatButtonModule,
    MatInputModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

Create Material Table

Now update the App Component template to implement a Material Datatable. The Material datatable is created by adding the <table> with the mat-table directive.

The [dataSource] property takes a collection of data passed to the data table.

Each column is created by adding an ng-container element with matColumnDef identifier. The last column is for action links to Edit and Delete operations. We are calling the openDialog() method and passing action needs to be performed. We’ll add the method definition later in the component class.

# Update HTML Template

Update the app.component.html as shown below:

<!-- app.component.html -->
<div class="container text-center">

  <button mat-button (click)="openDialog('Add',{})" mat-flat-button color="primary">Add Row</button>

  <table mat-table [dataSource]="dataSource" #mytable class="my-table mat-elevation-z8">

    <!--- Note that these columns can be defined in any order.
        The actual rendered columns are set as a property on the row definition" -->

    <!-- Id Column -->
    <ng-container matColumnDef="id">
      <th mat-header-cell *matHeaderCellDef> ID. </th>
      <td mat-cell *matCellDef="let element"> {{element.id}} </td>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef> Name </th>
      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
    </ng-container>

    <!-- Action Column -->
    <ng-container matColumnDef="action">
      <th mat-header-cell *matHeaderCellDef> Action </th>
      <td mat-cell *matCellDef="let element" class="action-link"> 
        <a (click)="openDialog('Update',element)">Edit</a> | 
        <a (click)="openDialog('Delete',element)">Delete</a>  
      </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>

</div>

The #mytable is a template variable which we will use to refresh table data by calling renderRows() method inside the class component.

*matHeaderRowDef takes an array of columns we want to show in the table.
matColumnDef property on the column is the same as a key in the JSON data object.
*matHeaderCellDef have the text of columns in the table header.
# Update App Component Class
Now replace the following code in app.component.ts file
//app.component.ts
import { Component, ViewChild } from '@angular/core';

import { MatTable } from '@angular/material/table';
import { MatDialog } from '@angular/material/dialog';
import { DialogBoxComponent } from './dialog-box/dialog-box.component';

export interface UsersData {
  name: string;
  id: number;
}

const ELEMENT_DATA: UsersData[] = [
  {id: 1560608769632, name: 'Artificial Intelligence'},
  {id: 1560608796014, name: 'Machine Learning'},
  {id: 1560608787815, name: 'Robotic Process Automation'},
  {id: 1560608805101, name: 'Blockchain'}
];
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  displayedColumns: string[] = ['id', 'name', 'action'];
  dataSource = ELEMENT_DATA;

  @ViewChild(MatTable,{static:true}) table: MatTable<any>;

  constructor(public dialog: MatDialog) {}

  openDialog(action,obj) {
    obj.action = action;
    const dialogRef = this.dialog.open(DialogBoxComponent, {
      width: '250px',
      data:obj
    });

    dialogRef.afterClosed().subscribe(result => {
      if(result.event == 'Add'){
        this.addRowData(result.data);
      }else if(result.event == 'Update'){
        this.updateRowData(result.data);
      }else if(result.event == 'Delete'){
        this.deleteRowData(result.data);
      }
    });
  }

  addRowData(row_obj){
    var d = new Date();
    this.dataSource.push({
      id:d.getTime(),
      name:row_obj.name
    });
    this.table.renderRows();
    
  }
  updateRowData(row_obj){
    this.dataSource = this.dataSource.filter((value,key)=>{
      if(value.id == row_obj.id){
        value.name = row_obj.name;
      }
      return true;
    });
  }
  deleteRowData(row_obj){
    this.dataSource = this.dataSource.filter((value,key)=>{
      return value.id != row_obj.id;
    });
  }
}

let’s back to app.component.ts explanation. Here we have Table data to populate and @ViewChild to get table reference.

The openDialog() method is getting action string for Add, Update and Delete and obj as row object to pass in open() method

Also subscribed to Dialog close event using afterClosed()method calling addRowData(), updateRowData() and deleteRowData() as per event sent back from DialogBoxComponent close() method

  openDialog(action,obj) {
    obj.action = action;
    const dialogRef = this.dialog.open(DialogBoxComponent, {
      width: '250px',
      data:obj
    });

    dialogRef.afterClosed().subscribe(result => {
      if(result.event == 'Add'){
        this.addRowData(result.data);
      }else if(result.event == 'Update'){
        this.updateRowData(result.data);
      }else if(result.event == 'Delete'){
        this.deleteRowData(result.data);
      }
    });
  }

 

Create a Dialog Component

To Add, Update and Delete operations, we are using the Material Dialog. For defining a custom component for Dialog, run following ng command.
$ ng generate component dialog-box --skipTests=true

--skipTests=true will create component without spec.ts test files

# Update the Dialog Component

Update the dialog-box.component.ts file with following code:

//dialog-box.component.ts
import { Component, Inject, Optional } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';

export interface UsersData {
  name: string;
  id: number;
}


@Component({
  selector: 'app-dialog-box',
  templateUrl: './dialog-box.component.html',
  styleUrls: ['./dialog-box.component.scss']
})
export class DialogBoxComponent {

  action:string;
  local_data:any;

  constructor(
    public dialogRef: MatDialogRef<DialogBoxComponent>,
    //@Optional() is used to prevent error if no data is passed
    @Optional() @Inject(MAT_DIALOG_DATA) public data: UsersData) {
    console.log(data);
    this.local_data = {...data};
    this.action = this.local_data.action;
  }

  doAction(){
    this.dialogRef.close({event:this.action,data:this.local_data});
  }

  closeDialog(){
    this.dialogRef.close({event:'Cancel'});
  }

}
The MAT_DIALOG_DATA is used to get data passed in the open method’s data parameter from the App component. The MatDialogRef class is used to perform actions on opened Dialog modal. This will be used to close Dialog after the action is successfully performed.
In the dialog-box.component.html file add following code with a heading, input field and buttons to perform action based operation.
<!-- dialog-box.component.html -->
<h1 mat-dialog-title>Row Action :: <strong>{{action}}</strong></h1>
<div mat-dialog-content>
  <mat-form-field *ngIf="action != 'Delete'; else elseTemplate">
    <input placeholder="{{action}} Name" matInput [(ngModel)]="local_data.name">
  </mat-form-field>
  <ng-template #elseTemplate>
    Sure to delete <b>{{local_data.name}}</b>?
  </ng-template>
</div>
<div mat-dialog-actions>
  <button mat-button (click)="doAction()">{{action}}</button>
  <button mat-button (click)="closeDialog()" mat-flat-button color="warn">Cancel</button>
</div>

 

 

Also, check how to add extra rows on the Angular Material table and implement Expand/ Collapse functionality by clicking inside each row and multiple rows by clicking on the master button. The master button will toggle expand/  collapse on multiple rows … Read more

 

That’s it now run the application using

$ ng serve --open

it will look something like this

 

If you see some errors like this when opening the Dialog: 

Can’t bind to ‘ngIf’ since it isn’t a known property of ‘mat-form-field’.
logUnknownPropertyError
Can’t bind to ‘ngIfElse’ since it isn’t a known property of ‘mat-form-field’.

Then make sure you have added the DialogBoxComponent under the declarations array in the app.module.ts file.

 

Also, check

 

 

Conclusion

So here we used Angular Material to build a Datatable with Edit, Delete and Add actions to operate on table rows using Dialog. We also get to know How to pass data from the parent component to the Dialog box and get back a response in the parent component.

Subscribe
Notify of
guest
10 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Sacha SAID

Problem : the console say me “this.dialog.open is not a function” when i press button addRow
Do you know why ??????

fahim

Do I need to create the dialog-box.component.ts and dialog-box.component.html file? Can I add the conetnt of dialog-box.component.ts into app.module.ts? I get error on line 16 and 24 of file dialog-box.component.ts.

In ng generate component dialog-boxx --skipTests=true command, is there typo boxx?

In the browser, clicking the buttons only shows message that the ‘dialog-box works!’.

Sachin Johnson

Great Example!

Sana

I get the error “TypeError: this.dataSource.filter is not a function
at AppComponent” when I try to update a value from the dialog box. I read somewhere that this is not support for Angular 6 and above versions. any idea about this?

Mike B.

I’m confused – I don’t see any reference to mytable in the ts code. how does the code call renderRows on it?

ayaaz mayet

why is this required because it stops my code from working – {static:true} ?

aravind

How to do pagination, sorting in the example you have provided.