Ngx-datatable: CSV Export

Created on 10 Jun 2017  路  9Comments  路  Source: swimlane/ngx-datatable








I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[X ] feature request
[ ] support request => Please do not submit support request here, post on Stackoverflow or Gitter

Can you implement export of the datatable as a csv please?

Thanks

Most helpful comment

I agree that csv export has been a must feature for datatables lately. If you are not planning build it, would you give an example or any tips how to implement it with ngx-datatable. I think that would be really helpful for many developers 馃憤

All 9 comments

Explicitly mentioned as out of scope.
https://swimlane.gitbooks.io/ngx-datatable/introduction/architecture.html

You're able to build this yourself with what ngx-datatable gives you

Sorry for asking

I agree that csv export has been a must feature for datatables lately. If you are not planning build it, would you give an example or any tips how to implement it with ngx-datatable. I think that would be really helpful for many developers 馃憤

Here is a first attempt (only using ngx-datatable for 1 month and Angular 4 for 2 months so bear with me please).

Be sure to install angular2-csv, first:
npm install --save angular2-csv

html part

<ngx-datatable
          #dataTable
          class="light striped"
          [rows]="rows"
...
</ngx-datatable>

controller part

@ViewChild('dataTable')  public dataTable: DatatableComponent;
...
exportAsCSV() {
        const columns: TableColumn[] = this.dataTable.columns || this.dataTable._internalColumns;
        const headers =
            columns
                .map((column: TableColumn) => column.name)
                .filter((e) => e);  // remove column without name (i.e. falsy value)

        const rows: any[] = this.dataTable.rows.map((row) => {
            let r = {};
            columns.forEach((column) => {
                if (!column.name) { return; }   // ignore column without name
                if (column.prop) {
                    let prop = column.prop;
                    r[prop] = (typeof row[prop] === 'boolean') ? (row[prop]) ? 'Yes'
                                                                             : 'No'
                                                               : row[prop];
                } else {
                    // special cases handled here
                }
            })
            return r;
        });

        const options = {
            fieldSeparator  : ',',
            quoteStrings    : '"',
            decimalseparator: '.',
            showLabels      : true,
            headers         : headers,
            showTitle       : false,
            title           : 'Report',
            useBom          : true,
        };
        return new Angular2Csv(rows, 'report', options);
    }

_Disclaimer
I'm using internal properties (_internalColumns) since the getter wasn't giving me any data (??). I guess we can achieve way better by using other methods of TableColumn but it should take care of simple use cases.
This is NOT production-ready code whatsoever, use it at your own risk 馃挜!_

some definitive solution to export to CSV ?? I miss jquery data-table T.T !!!

Has anyone successed to implement this grid download in csv facility ?

There's a big issue with @guillaumegarcia13's code, as it just uses the row's data and not the datatable's templates. most of the data (atleast in my projects) is objects and is used in complex ways in templates.

example -

<ngx-datatable-column prop="PropName" name="Prop Name">
            <ng-template let-value="value" ngx-datatable-cell-template>
                {{value.Type | TypeDisplayNamePipe}} ({{value.Desc}})
            </ng-template>
</ngx-datatable-column>

Also - the code doesn't support nested properties - "PropName.PropName2" in the column.prop.
Added a portion to deal with that -

import { DatatableComponent, TableColumn } from "@swimlane/ngx-datatable";
import { Angular5Csv } from 'angular5-csv/Angular5-csv';

export function exportAsCSV(dataTable: DatatableComponent) {
  const columns: TableColumn[] = dataTable.columns || dataTable._internalColumns;
  const headers =
      columns
          .map((column: TableColumn) => column.name)
          .filter((e) => e);  // remove column without name (i.e. falsy value)

  const rows: any[] = dataTable.rows.map((row) => {
      let r = {};
      columns.forEach((column) => {
          if (!column.name) { return; }   // ignore column without name
          if (column.prop) {
              let prop = column.prop.toString();
              let value = getNestedPropertyValue(row, prop);

              r[prop] = (typeof value === 'boolean') ? (value ? 'Yes' : 'No') : value;
          } else {
              // special cases handled here
          }
      })
      return r;
  });

  const options = {
      fieldSeparator  : ',',
      quoteStrings    : '"',
      decimalseparator: '.',
      showLabels      : true,
      headers         : headers,
      showTitle       : false,
      title           : 'Report',
      useBom          : true
  };

  return new Angular5Csv(rows, 'report', options);
}

function getNestedPropertyValue(object: any, nestedPropertyName: string) {
    var dotIndex = nestedPropertyName.indexOf(".");
    if (dotIndex == -1) {
        return object[nestedPropertyName];
    } else {
        var propertyName = nestedPropertyName.substring(0, dotIndex);
        var nestedPropertyNames = nestedPropertyName.substring(dotIndex + 1);

        return getNestedPropertyValue(object[propertyName], nestedPropertyNames);
    }
}

I got to the point of rendering the cell-templates and taking the text off of the html element.

I created a new component -

import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'dynamic-template',
  template: `
    <ng-template
        [ngTemplateOutlet]="template"
        [ngTemplateOutletContext]="context"> 
    </ng-template>
  `
})
export class TemplateComponent {
    @Input() template;
    @Input() context;
}

and the new code to use it :

import { DatatableComponent, TableColumn } from "@swimlane/ngx-datatable";
import { Angular5Csv } from 'angular5-csv/Angular5-csv';
import { TemplateComponent } from "./components/common/template.component";
import { ComponentFactoryResolver, Injector } from "@angular/core";

export function exportAsCSV(dataTable: DatatableComponent, resolver: ComponentFactoryResolver, injector: Injector) {
    const columns: TableColumn[] = dataTable.columns || dataTable._internalColumns;
    const headers =
        columns
            .map((column: TableColumn) => column.name)
            .filter((e) => e);  // remove column without name (i.e. falsy value)

    const rows: any[] = dataTable.rows.map((row) => {
        let r = {};

        columns.forEach((column) => {
            if (!column.name) { return; }   // ignore column without name

            var prop
            var propValue;
            if (column.prop) {
                prop = column.prop.toString();
                propValue = getNestedPropertyValue(row, prop);
            }

            if (column.cellTemplate) {
                r[column.name] = getRenderedTemplateText(column.cellTemplate, propValue, row, resolver, injector);
            } else {
                r[column.name] = (typeof propValue === 'boolean') ? (propValue ? 'Yes' : 'No') : propValue;
            }
        });

        return r;
    });

    const options = {
        fieldSeparator: ',',
        quoteStrings: '"',
        decimalseparator: '.',
        showLabels: true,
        headers: headers,
        showTitle: false,
        title: 'Report',
        useBom: true
    };

    return new Angular5Csv(rows, 'report', options);
}

function getRenderedTemplateText(template, value, row, resolver: ComponentFactoryResolver, injector: Injector) {
    const factory = resolver.resolveComponentFactory(TemplateComponent);
    const component = factory.create(injector);

    component.instance.template = template;
    component.instance.context = { value: value, row: row };
    component.changeDetectorRef.detectChanges();

    return component.location.nativeElement.textContent.trim();
}

function getNestedPropertyValue(object: any, nestedPropertyName: string) {
    var dotIndex = nestedPropertyName.indexOf(".");
    if (dotIndex == -1) {
        return object[nestedPropertyName];
    } else {
        var propertyName = nestedPropertyName.substring(0, dotIndex);
        var nestedPropertyNames = nestedPropertyName.substring(dotIndex + 1);

        return getNestedPropertyValue(object[propertyName], nestedPropertyNames);
    }
}

When there's a template connected to a column, I create a new TemplateComponent with the right template and context, render it and take the text, If not I use the old code to just get the value from the row based on the nested property.

I created the context based on the data I needed
component.instance.context = { value: value, row: row };
If you use more data, add it to the context too.

Also I changed it from r[prop] to r[column.name] as I want the csv to reflect what's on screen, there's an issue with columns with the same name atm which you can fix by appending _# at the end of names or looking more into Angular5Csv's options or code.

I might make this class a service so I can inject the dependencies directly, but this works great for me.

I have a better PRACTICAL solution for this.

In most of the cases, the data is comming from API.

So, it would be great if we convert the data to csv directly.
Yes, It is possible...

Step 1 : npm install @ctrl/ngx-csv

Step 2 : module.ts
```
@NgModule({
declarations: [
...
...
...
],
imports: [
...
CsvModule,
...
]
})

Step 3 : **component.html**

CSV
```

Hope you find this solution helpfull :)

Was this page helpful?
0 / 5 - 0 ratings