Blogs about Atlas, Microsoft 365, Teams

Export data to CSV with Typescript without format issues

Written by Jorge Garcia | Jan 31, 2020 3:30:00 PM

On many occasions, users need to export the information shown in a table or list on our website and without a doubt, it is our responsibility to facilitate this task. And what better way than allowing to export the information to CSV with a single click.

 

Therefore, today I will teach you to create a very simple and reusable control based on Typescript to be able to generate CSV documents directly from any browser.

Let's get to the point, the method we have created is this:

export const exportToCsv = (filename: string, rows: object[], headers?: string[]): void => {
if (!rows || !rows.length) {
return;
}
const separator: string = ",";

const keys: string[] = Object.keys(rows[0]);

let columHearders: string[];

if (headers) {
columHearders = headers;
} else {
columHearders = keys;
}

const csvContent =
"sep=,\n" +
columHearders.join(separator) +
'\n' +
rows.map(row => {
return keys.map(k => {
let cell = row[k] === null || row[k] === undefined ? '' : row[k];

cell = cell instanceof Date
? cell.toLocaleString()
: cell.toString().replace(/"/g, '""');

if (cell.search(/("|,|\n)/g) >= 0) {
cell = `"${cell}"`;
}
return cell;
}).join(separator);
}).join('\n');

const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // In case of IE 10+
navigator.msSaveBlob(blob, filename);
} else {
const link = document.createElement('a');
if (link.download !== undefined) {
// Browsers that support HTML5 download attribute
const 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);
}
}
}

In summary, this method received 3 parameters:
  • filename: A string that specifies the name of the file that we are going to generate (don't forget includes the .csv extension)

  • rows: An object array with the information that we are going to export. Although it is an array of objects, I recommend that they all be of the same class and not complex. Keep in mind that all the attributes of that object will be exported to the csv.

  • headers: This is an optional parameter, which allows us to indicate the name that each column of the final document will have. It is important that this array of strings is ordered so that the headers match the values. If we do not include this parameter, the name of the row attributes will be used as headers


OK. That is all? Well ... not exactly. 

This code will help you in most cases for almost all browsers, however, there is a case that will give you problems: Internet Explorer and date formats.

 

And if our data includes a formatted date, downloading it from most browsers will look correctly:

 

However, from Internet Explorer we will get this:

 



It seems that Internet Explorer adds some extra characters when encoding formatted dates.

 

Ok… And what is the solution?

 

After a long investigation on how Internet Explorer encodes the dates, I must say that the best solution I have found for this issue (apart from ignoring it), is to eliminate the invisible characters that Internet Explorer adds to the dates. To do this, just modify the above method a bit and add this:

if (navigator.msSaveBlob) {
cell = cell.replace(/[^\x00-\x7F]/g, ""); //remove non-ascii characters
}

 

This piece of code will eliminate all non-ascii characters, making sure that all those unexpected features added by Internet Explorer are eliminated.

 

Therefore, the final code will be:

 

 

 

export const exportToCsv = (filename: string, rows: object[], headers?: string[]): void => {
if (!rows || !rows.length) {
return;
}
const separator: string = ",";

const keys: string[] = Object.keys(rows[0]);

let columHearders: string[];

if (headers) {
columHearders = headers;
} else {
columHearders = keys;
}

const csvContent =
"sep=,\n" +
columHearders.join(separator) +
'\n' +
rows.map(row => {
return keys.map(k => {
let cell = row[k] === null || row[k] === undefined ? '' : row[k];

cell = cell instanceof Date
? cell.toLocaleString()
: cell.toString().replace(/"/g, '""');

if (navigator.msSaveBlob) {
cell = cell.replace(/[^\x00-\x7F]/g, ""); //remove non-ascii characters
}
if (cell.search(/("|,|\n)/g) >= 0) {
cell = `"${cell}"`;
}
return cell;
}).join(separator);
}).join('\n');

const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // In case of IE 10+
navigator.msSaveBlob(blob, filename);
} else {
const link = document.createElement('a');
if (link.download !== undefined) {
// Browsers that support HTML5 download attribute
const 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);
}
}
}

 

Note: You should keep in mind that if the data you are going to export includes non-ascii characters you will have to make some extra modifications to allow these characters.

 

Now our method is complete, it only remains to call it from wherever you consider.  You can check the full method here.

 

Have a nice day and may the power of the CSV be with you!