0

I am currently trying to export some data from my application in either Excel or CSV. What is the best way to accomplish this? Should I export from the backend, or export once I have the data client side using a library within Angular 2? My Web API 2 controller currently produces a list and then sends it as JSON to the front end.

That all works, I am just struggling with exporting the list.

Here is a sample of what I am doing

 [HttpGet]
 [Route("/api/preview/{item}")]
 public IActionResult Preview(string item)
 {      
     if (item!= null)
     {
         var preview = _context.dbPreview.FromSql("Exec sampleStoredProcedure {0}, 1", item).ToList();

         return Ok(preview);
     }
 }

That is how I am generating my data that is sent to Angular 2.

I can provide any Angular 2 code if it is necessary but it is just a normal service. Was not sure if there was some library that worked well with Angular 2 to do an export. I've seen some things for javascript but alaSQL but it does not seem like it would work with Angular 2.

Any ideas?

Tseng
  • 61,549
  • 15
  • 193
  • 205
dev53
  • 404
  • 10
  • 26
  • You have the data client side then I think the best is to use javascript to create a csv file. e.g. [PrimeNG](http://www.primefaces.org/primeng/#/datatable/export) can export csv files. (Server side is possible as well.) – AWolf Nov 09 '16 at 20:50
  • I looked at that PrimeNG datatable export but I was not sure if there was a way to trigger the export without showing the datatable itself. The table is amount of data is pretty large and displaying it on the page does not seem reasonable. That is why I wanted to export it without displaying it on the page. Any ideas? @AWolf – dev53 Nov 09 '16 at 20:56
  • Powershell has a cmdlet called Export-csv that is very easy to use, provided you have your data organized well. Don't know if you want to add powershell to your toolbox. – Walter Mitty Nov 09 '16 at 21:48
  • Yeah not sure if adding powershell to the web app would make sense for me but I appreciate the comment and idea. Thank you. – dev53 Nov 09 '16 at 21:52

2 Answers2

2

I've looked at the source code from PrimeNG DataTable and I think you can use the exportCSV code for exporting a csv of your data.

The "trick" is to generate a string starting with data:text/csv;charset=utf-8 and make this downloadable by the user.

Something like the following code should work for you (maybe you need to modify it a bit so it fits to your data).

Most of the code is copied from PrimeNG except the download method. That method is copied from a SO answer.

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

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    title = 'app works!';

    csvSeparator = ';';

    value = [
        { name: 'A3', year: 2013, brand: 'Audi' },
        { name: 'Z3', year: 2015, brand: 'BMW' }
    ];

    columns = [
        { field: 'name', header: 'Name' },
        { field: 'year', header: 'Production data' },
        { field: 'brand', header: 'Brand' },
    ];

    constructor() {

        console.log(this.value);
        this.exportCSV('cars.csv'); // just for show casing --> later triggered by a click on a button
    }

    download(text, filename) {
        let element = document.createElement('a');
        element.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', filename);

        element.style.display = 'none';
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
    }

    exportCSV(filename) {
        let data = this.value, csv = '';
        // csv = "data:text/csv;charset=utf-8,";

        //headers
        for (let i = 0; i < this.columns.length; i++) {
            if (this.columns[i].field) {
                csv += this.columns[i].field;

                if (i < (this.columns.length - 1)) {
                    csv += this.csvSeparator;
                }
            }
        }

        //body        
        this.value.forEach((record, j) => {
            csv += '\n';
            for (let i = 0; i < this.columns.length; i++) {
                if (this.columns[i].field) {
                    console.log(record[this.columns[i].field]);
                    // resolveFieldData seems to check if field is nested e.g. data.something --> probably not needed
                    csv += record[this.columns[i].field]; //this.resolveFieldData(record, this.columns[i].field);

                    if (i < (this.columns.length - 1)) {
                        csv += this.csvSeparator;
                    }
                }
            }
        });
        // console.log(csv);
        // window.open(encodeURI(csv)); // doesn't display a filename!
        this.download(csv, filename);
    }

    // resolveFieldData(data: any, field: string): any {
    //     if(data && field) {
    //         if(field.indexOf('.') == -1) {
    //             return data[field];
    //         }
    //         else {
    //             let fields: string[] = field.split('.');
    //             let value = data;
    //             for(var i = 0, len = fields.length; i < len; ++i) {
    //                 value = value[fields[i]];
    //             }
    //             return value;
    //         }
    //     }
    //     else {
    //         return null;
    //     }
    // }


}
Community
  • 1
  • 1
AWolf
  • 8,770
  • 5
  • 33
  • 39
  • Thank you so much. This is working for me. The only issue is that at the moment it does not work in internet explorer. Going to see if I can find some workaround for that. – dev53 Nov 10 '16 at 14:29
0

AWolfs answer got me on the right track but I did some tweaking to get it working with Internet Explorer.

This function converts my array to my string for my csv file. (I had to create a new object that was my column headers). I then just pass the data that is generated by my service to the function and it does the parsing for me. For more complex data I believe you would need to do some additional logic but I have basic text so it all worked out for me.

exportCSV(filename, CsvData) {
    let data = CsvData, csv = '';

    console.log(data);
    //headers
    for (let i = 0; i < this.columns.length; i++) {
        if (this.columns[i].field) {
            csv += this.columns[i].field;

            if (i < (this.columns.length - 1)) {
                csv += this.csvSeparator;
            }
        }
    }

    //body        
    CsvData.forEach((record, j) => {
        csv += '\n';
        for (let i = 0; i < this.columns.length; i++) {
            if (this.columns[i].field) {
                console.log(record[this.columns[i].field]);
                csv += record[this.columns[i].field]; 
                if (i < (this.columns.length - 1)) {
                    csv += this.csvSeparator;
                }
            }
        }
    });
    this.DownloadFile(csv, filename);
}

That was pretty much the same as AWolfs answer but I had to make some modifications to the DownloadFile function to get it to work with additional browsers. This function just accepts the huge string that makes up your .CSV file and the filename.

 DownloadFile(text, filename) {
    console.log(text);
    var blob = new Blob([text], { type: 'text/csv;charset=utf-8;' });
    if (navigator.msSaveBlob) { // IE 10+
        navigator.msSaveBlob(blob, filename);
    }
    else //create a link and click it
    {
        var link = document.createElement("a");
        if (link.download !== undefined) // feature detection
        {
            // Browsers that support HTML5 download attribute
            var url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download", filename);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
}

This code needs cleaned up but I wanted to update my question with an answer for anyone who was struggling with the same thing. This should at least get you started. This works in both Chrome and IE.

Thanks.

dev53
  • 404
  • 10
  • 26
  • Looks good and you're right IE is really a problem with my code because it doesn't support `download` attribute (Safari is also not supporting it). After some googling I've found that [file-saver](https://www.npmjs.com/package/file-saver) is doing a pretty good job. So you don't need the download method and you can use `filesaver.saveAs(blob, filename);`. Importing the module is a bit tricky but with this [issue at git](https://github.com/angular/angular-cli/issues/999) it will work. – AWolf Nov 11 '16 at 18:49