0

How should I make angular binding work on dynamically added DOM elements? I am using ag-grid (ng2) for datatables. Based on certain conditions, i am using different column rendering.

      columnDef.cellRenderer = function (params) {
        return `<div><i class='fa ${params.value}'></i></div>`;
      };

In this, i want to add a click function to the icon like this:

      columnDef.cellRenderer = function (params) {
        return `<div><i (click)='iconClicked()' class='fa ${params.value}'></i></div>`;
      };

How do I make these click bindings work in angular 2 ?

ashfaq.p
  • 5,379
  • 21
  • 35

3 Answers3

2

You can build components dynamically like explained in Equivalent of $compile in Angular 2 to be able to pass HTML with Angular event and value bindings.

or you can use imperative event binding

export class MyComponent {
  constructor(private elRef:ElementRef) {}

  someMethod() {
    columnDef.cellRenderer = (params) => {
      return `<div><i id="addClick" class='fa ${params.value}'></i></div>`;
      this.elRef.nativeElement.querySelector('#addClick')
      .addEventListener('click',  this.iconClicked.bind(this));
    };
  }

  iconClicked(e) {
  }
}
Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Do I need to take care of removing event listener from these elements when component is destroyed ? – ashfaq.p Jan 22 '17 at 12:41
  • I'm never 100% sure when this is actually necessary. AFAIK if you want to keep the event listener until the component `MyComponent` is destroyed or until the HTML snipped you added gets removed from the DOM, then you don't need to remove the event listener. – Günter Zöchbauer Jan 22 '17 at 12:43
1

As micronyks said, you can create components dynamically by using ComponentFactoryResolver (NOT DynamicComponentResolver, it no longer exists) and you can find examples on S.O. (e.g. https://stackoverflow.com/a/36566919/1153681).

But it won't work in your situation because:

  • You don't want to create a whole component, but only add a piece of markup to an existing component.
  • You're not the one creating the markup, ag-grid is.

Since you're in ag-grid context, why don't you use ag-grid's API instead of Angular's? A quick look at their docs shows that a grid has a property onCellClicked(params) that takes a function callback which gets called when a cell is clicked.

Then hopefully you can trigger some Angular code from that callback.

Community
  • 1
  • 1
AngularChef
  • 13,797
  • 8
  • 53
  • 69
0

You don't need to use ComponentFactoryResolver directly if you want dynamic Angular 2 components in ag-grid - you can use ag-grid's provided Angular 2 interface for this.

Let's say you have the following simple Components:

// CubeComponent
import {Component} from '@angular/core';
import {AgRendererComponent} from 'ag-grid-ng2/main';

@Component({
    selector: 'cube-cell',
    template: `{{valueCubed()}}`
})
export class CubeComponent implements AgRendererComponent {
    private params:any;
    private cubed:number;

    // called on init
    agInit(params:any):void {
        this.params = params;
        this.cubed = this.params.data.value * this.params.data.value * this.params.data.value;
    }

    public valueCubed():number {
        return this.cubed;
    }
}

// Square Component
import {Component} from '@angular/core';

import {AgRendererComponent} from 'ag-grid-ng2/main';

@Component({
    selector: 'square-cell',
    template: `{{valueSquared()}}`
})
export class SquareComponent implements AgRendererComponent {
    private params:any;

    agInit(params:any):void {
        this.params = params;
    }

    public valueSquared():number {
        return this.params.value * this.params.value;
    }
}

// from-component.component.html
<div style="width: 200px;">
    <button (click)="changeComponentType()">Change Component Type</button>
    <ag-grid-ng2 #agGrid style="width: 100%; height: 350px;" class="ag-fresh"
                 [gridOptions]="gridOptions">
    </ag-grid-ng2>
</div>

// from-component.component.ts
import {Component} from '@angular/core';

import {GridOptions} from 'ag-grid/main';
import {SquareComponent} from "./square.component";
import {CubeComponent} from "./cube.component";

@Component({
    moduleId: module.id,
    selector: 'ag-from-component',
    templateUrl: 'from-component.component.html'
})
export class FromComponentComponent {
    public gridOptions:GridOptions;
    private currentComponentType : any = SquareComponent;

    constructor() {
        this.gridOptions = <GridOptions>{};
        this.gridOptions.rowData = this.createRowData();
        this.gridOptions.onGridReady = () => {
            this.setColumnDefs();
        }
    }

    public changeComponentType() {
        this.currentComponentType = this.currentComponentType === SquareComponent ? CubeComponent : SquareComponent;
        this.setColumnDefs();
    }

    private createRowData() {
        let rowData:any[] = [];

        for (var i = 0; i < 15; i++) {
            rowData.push({
                value: i
            });
        }

        return rowData;
    }

    private setColumnDefs():void {
        this.gridOptions.api.setColumnDefs([
            {
                headerName: "Dynamic Component",
                field: "value",
                cellRendererFramework: this.currentComponentType,
                width: 200
            }
        ])
    }
}

// app.module.ts
import {NgModule} from "@angular/core";
import {BrowserModule} from "@angular/platform-browser";
import {RouterModule, Routes} from "@angular/router";
// ag-grid
import {AgGridModule} from "ag-grid-ng2/main";
// application
import {AppComponent} from "./app.component";
// from component
import {FromComponentComponent} from "./from-component.component";
import {SquareComponent} from "./square.component";
import {CubeComponent} from "./cube.component";

const appRoutes:Routes = [
    {path: 'from-component', component: FromComponentComponent, data: {title: "Using Dynamic Components"}},
    {path: '', redirectTo: 'from-component', pathMatch: 'full'}
];

@NgModule({
    imports: [
        BrowserModule,
        RouterModule.forRoot(appRoutes),
        AgGridModule.withComponents(
            [
                SquareComponent,
                CubeComponent,
            ])
    ],
    declarations: [
        AppComponent,
        FromComponentComponent,
        SquareComponent,
        CubeComponent
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

Here the button will allow you to dynamically switch between the two components - this could obviously be done at runtime based on some condition you have.

Note too that it might be simpler for you to have one component, and in the actual output do the conditional logic - for example:

// called on init
agInit(params:any):void {
    this.params = params;

    if(this.params.isCube) {
        // cubed
        this.value = this.params.data.value * this.params.data.value * this.params.data.value;
    } else {
        // square
        this.value = this.params.data.value * this.params.data.value;
    }
}

You can find more information on how to use Angular 2 with ag-Grid here: https://www.ag-grid.com/best-angular-2-data-grid

With that in place, you can refer to https://github.com/ceolter/ag-grid-ng2-example/blob/master/systemjs_aot/app/clickable.component.ts for an example of using a component that supports click events in the grid. For all the examples take a look at https://github.com/ceolter/ag-grid-ng2-example which provides many examples, together with how to package them up with either systemjs, webpack or angular cli

Sean Landsman
  • 7,033
  • 2
  • 29
  • 33