angularbuttonag-gridag-grid-angular

Unable to disable button in ag-grid


I want to disable a button in a column using the click event of a button in another column in ag-grid.

I have used common service to disable it, but it disabled all the buttons in that column and I've also tried updating that column's field in params object(rowData), but didn't work.

Here is a sample on stackbltiz

Edit: Added source code

app.component.html

<h1>Hello World</h1>

<ag-grid-angular
  style="width: 500px; height: 350px;"
  class="ag-theme-alpine"
  [rowData]="rowData"
  [columnDefs]="columnDefs"
>
</ag-grid-angular>

app.component.ts

import { Component } from '@angular/core';
import { ColDef } from 'ag-grid-community';
import { ButtonRendererComponent } from './button-renderer/button-renderer.component';
@Component({
  selector: 'my-app',
  styleUrls: ['./app.component.css'],
  templateUrl: './app.component.html',
})
export class AppComponent {
  columnDefs: ColDef[] = [
    {
      field: 'make',
      headerName: 'Button1',
      cellRenderer: ButtonRendererComponent,
      cellRendererParams: {
        comp: 'label1',
      },
    },
    {
      field: 'model',
      headerName: 'Button1',
      cellRenderer: ButtonRendererComponent,
      cellRendererParams: {
        comp: 'label2',
      },
    },
    { field: 'price' },
  ];

  rowData = [
    {
      make: 'Toyota',
      model: 'Celica',
      price: 35000,
      button1: true,
      button2: false,
    },
    {
      make: 'Ford',
      model: 'Mondeo',
      price: 32000,
      button1: true, //fetched from DB
      button2: false, //fetched from DB
    },
    {
      make: 'Porsche',
      model: 'Boxster',
      price: 72000,
      button1: true, //fetched from DB
      button2: false, //fetched from DB
    },
  ];
}

button-renderer.component.html

<button
  *ngIf="params['comp'] == 'label1'"
  mat-flat-button
  color="primary"
  (click)="clickButton1()"
  [disabled]="isButton1Disabled"
>
  Button1
</button>
<button
  *ngIf="params['comp'] == 'label2'"
  mat-flat-button
  color="primary"
  (click)="clickButton2()"
  [disabled]="comSvc.isButtonDisabled"
>
  Button2
</button>

button-renderer.component.ts

import { Component, OnInit } from '@angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { CommonService } from '../common.service';

@Component({
  selector: 'app-button-renderer',
  templateUrl: './button-renderer.component.html',
  styleUrls: ['./button-renderer.component.css'],
})
export class ButtonRendererComponent implements ICellRendererAngularComp {
  params: any;
  isButton1Disabled: boolean = false;
  isButton2Disabled: boolean = true;
  agInit(params: ICellRendererParams): void {
    this.params = params;
  }

  // gets called whenever the cell refreshes
  refresh(params: ICellRendererParams): boolean {
    // set value into cell again
    return true;
  }
  constructor(public comSvc: CommonService) {}

  ngOnInit() {}
  clickButton1() {
    console.log('Button 1 is clicked');
    // this.isButton2Disabled = true;
    // this.params.button2 = true;
    this.comSvc.isButtonDisabled = true;
    //DB update call
  }
  clickButton2() {
    console.log('Button 2 is clicked');
    //DB update call
  }
}

Packages:

Angular: 16.2.0 
ag-grid-angular: 30.2.0

Solution

  • This is normal to disable every buttons with you current code, as the service is common for every components and you have only one state for the button. This means all buttons have the same disabled state.

    Solution 1: Use a service keeping all row disabled states

    A solution is to keep your service and have a map of id rows and disabled state

    cell renderer component html:

    <button *ngIf="params['comp'] == 'label1'" mat-flat-button color="primary" (click)="clickButton1()">
      Button1
    </button>
    <button *ngIf="params['comp'] == 'label2'" mat-flat-button color="primary" (click)="clickButton2()" [disabled]="comSvc.isDisabled(this.params.rowIndex)">
      Button2
    </button>
    

    cell renderer component ts:

    import { Component, OnInit } from '@angular/core';
    import { ICellRendererAngularComp } from 'ag-grid-angular';
    import { ICellRendererParams } from 'ag-grid-community';
    import { CommonService } from '../common.service';
    
    @Component({
      selector: 'app-button-renderer',
      templateUrl: './button-renderer.component.html',
    })
    export class ButtonRendererComponent implements ICellRendererAngularComp {
      params: any;
    
      agInit(params: ICellRendererParams): void {
        this.params = params;
      }
    
      // gets called whenever the cell refreshes
      refresh(params: ICellRendererParams): boolean {
        // set value into cell again
        return true;
      }
    
      constructor(public comSvc: CommonService) {}
    
      clickButton1() {
        console.log(`Button 1 is clicked on row index${this.params.rowIndex}`);
        this.comSvc.disableButtonOnRow(this.params.rowIndex);
        //DB update call
      }
      clickButton2() {
        console.log('Button 2 is clicked');
        //DB update call
      }
    }
    

    the service:

    import { Injectable } from '@angular/core';
    
    @Injectable()
    export class CommonService {
      private disabledButtonByRow = new Map<number, boolean>();
    
      public disableButtonOnRow(rowId: number): void {
        this.disabledButtonByRow.set(rowId, true);
      }
    
      public isDisabled(rowId: number): boolean {
        console.log('is disabled is called!' + rowId);
        return !!(
          this.disabledButtonByRow.has(rowId) && this.disabledButtonByRow.get(rowId)
        );
      }
    }
    

    Solution 2: Put both buttons in the same cell renderer

    Why is there 2 buttons in one cell renderer component with a if, when you share no logic between them and only display one. Things get way easier when you display both buttons in one cell as you can share the logic.

    cell renderer component html:

    <button mat-flat-button color="primary" (click)="clickButton1()">
      Button1
    </button>
    <button mat-flat-button color="primary" (click)="clickButton2()" [disabled]="button2DisabledState">
      Button2
    </button>
    

    cell renderer component ts:

    import { Component } from '@angular/core';
    import { ICellRendererAngularComp } from 'ag-grid-angular';
    import { ICellRendererParams } from 'ag-grid-community';
    
    @Component({
      selector: 'app-button-renderer',
      templateUrl: './button-renderer.component.html',
    })
    export class ButtonRendererComponent implements ICellRendererAngularComp {
      button2DisabledState= false;
    
      agInit(): void {
      }
    
      // gets called whenever the cell refreshes
      refresh(params: ICellRendererParams): boolean {
        // set value into cell again
        return false;
      }
    
      clickButton1() {
        this.button2DisabledState= true;
        //DB update call
      }
      clickButton2() {
        console.log('Button 2 is clicked');
        //DB update call
      }
    }
    

    table component ts:

    import { Component } from '@angular/core';
    import { ColDef } from 'ag-grid-community';
    import { ButtonRendererComponent } from './button-renderer/button-renderer.component';
    @Component({
      selector: 'my-app',
      styleUrls: ['./app.component.css'],
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      columnDefs: ColDef[] = [
        {
          field: 'make',
          headerName: 'Button1 & 2',
          cellRenderer: ButtonRendererComponent,
          flex:2
        },
        { field: 'price',
          flex: 1 
        },
      ];
    
      rowData = [
        {
          make: 'Toyota',
          model: 'Celica',
          price: 35000,
          button1: true,
          button2: false,
        },
        {
          make: 'Ford',
          model: 'Mondeo',
          price: 32000,
          button1: true, //fetched from DB
          button2: false, //fetched from DB
        },
        {
          make: 'Porsche',
          model: 'Boxster',
          price: 72000,
          button1: true, //fetched from DB
          button2: false, //fetched from DB
        },
      ];
    }
    

    Solution 3: Update row data to refresh the cell renderers

    cell renderer component html:

    <button
      mat-flat-button
      color="primary"
      (click)="params.actionOnClick()"
      [disabled]="params.value"
    >
      {{params.buttonName}}
    </button>
    

    cell renderer component ts:

    import { Component, OnInit } from '@angular/core';
    import { ICellRendererAngularComp } from 'ag-grid-angular';
    import { ICellRendererParams } from 'ag-grid-community';
    
    @Component({
      selector: 'app-button-renderer',
      templateUrl: './button-renderer.component.html',
    })
    export class ButtonRendererComponent implements ICellRendererAngularComp {
      params: any;
    
      agInit(params: ICellRendererParams): void {
        this.params = params;
      }
    
      // gets called whenever the cell refreshes
      refresh(params: ICellRendererParams): boolean {
        // set value into cell again
        return false;
      }
    }
    

    table component ts:

    import { Component } from '@angular/core';
    import { ColDef } from 'ag-grid-community';
    import { ButtonRendererComponent } from './button-renderer/button-renderer.component';
    @Component({
      selector: 'my-app',
      styleUrls: ['./app.component.css'],
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      columnDefs: ColDef[] = [
        {
          field: 'button1',
          headerName: 'Button1',
          cellRenderer: ButtonRendererComponent,
          cellRendererParams: (params: any) => ({
            buttonName: 'Button 1',
            actionOnClick: () => {
              console.log(`Button 1 is clicked on row index${params.rowIndex}`);
              this.rowData[params.rowIndex].button2 = true;
              // force aggrid to detect changes in row Data
              this.rowData = [...this.rowData];
            },
          }),
        },
        {
          field: 'button2',
          headerName: 'Button2',
          cellRenderer: ButtonRendererComponent,
          cellRendererParams: {
            buttonName: 'Button 2',
            actionOnClick: () => {
              console.log('Button 2 is clicked');
              //DB update call
            },
          },
        },
        { field: 'price' },
      ];
    
      rowData = [
        {
          make: 'Toyota',
          model: 'Celica',
          price: 35000,
          button1: false,
          button2: false,
        },
        {
          make: 'Ford',
          model: 'Mondeo',
          price: 32000,
          button1: false, //fetched from DB
          button2: false, //fetched from DB
        },
        {
          make: 'Porsche',
          model: 'Boxster',
          price: 72000,
          button1: true, //fetched from DB
          button2: false, //fetched from DB
        },
      ];
    }
    

    The important part here, is this.rowData = [...this.rowData];, which helps aggrid to detect changes in the rowData