10

I am programming an application in angular2 that reads a csv file with a simply input in html:

<input type='file' name='userFile' id='file' >

I can access to the file at the component.ts:

ngOnInit() {

   var input = (<HTMLInputElement>document.getElementById("file"));
   input.addEventListener("change", function(event) {
      var files = input.files;
      var len = files.length;

         if (len) {
          console.log("Filename: " + files[0].name);
          console.log("Type: " + files[0].type);
          console.log("Size: " + files[0].size + " bytes");
    
         }

      }, false);

}

How can I read cell by cell a csv file uploaded using typescript, JavaScript or jQuery? (and which is the best way to do it).

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
  • You're not attaching the event in angular way. You should not directly access the dom using dom api. You can check the answers: https://stackoverflow.com/questions/31746837/reading-uploaded-text-file-contents-in-html – asmmahmud Oct 14 '17 at 12:39
  • What is the correct way to do it on typescript/angular? I am newie. –  Oct 14 '17 at 12:53
  • Where should I place the event function? –  Oct 14 '17 at 13:08

5 Answers5

13

Here is a example implementation angular way (Angular version 2+):

@Component({
  selector: 'app-my-file',
  template: `
   <div class="form-group">
        <input type="file" (change)="onFileSelect($event.target)" name="myfile">
   </div>
  `,
  styles: [``]
})
export class YourComponent implements OnInit {

    csvContent: string;
    
    constructor(){}
    ngOnInit(){
    }

    onFileLoad(fileLoadedEvent) {
            const textFromFileLoaded = fileLoadedEvent.target.result;              
            this.csvContent = textFromFileLoaded;     
            // alert(this.csvContent);
    }

    onFileSelect(input: HTMLInputElement) {
    
      const files = input.files;
      var content = this.csvContent;    
      if (files && files.length) {
         /*
          console.log("Filename: " + files[0].name);
          console.log("Type: " + files[0].type);
          console.log("Size: " + files[0].size + " bytes");
          */
          
          const fileToRead = files[0];
          
          const fileReader = new FileReader();
          fileReader.onload = this.onFileLoad;

          fileReader.readAsText(fileToLoad, "UTF-8");
      }
              
    }
}

Try it on StackBlitz

asmmahmud
  • 4,844
  • 2
  • 40
  • 47
  • Thank you for the example. I will use the reader outside implementations at a function as at the example. –  Oct 14 '17 at 15:58
  • Thank you that was very helpful. But I'm having an issue inside the onFileLoad function: when I pass in a method in the outer class the debugger says that the "this" is bound to the FileReader class... – lmngn23 Jun 12 '20 at 16:27
12

In addition to asmmahmud's answer, I'd like to add how you can actually parse the file content so you have rows and columns in an array (Angular with TypeScript). Add the property:

parsedCsv: string[][];

to the class YourComponent in his example. Then, update the onFileLoad event as follows:

onFileLoad(fileLoadedEvent): void {
 const csvSeparator = ';';
 const textFromFileLoaded = fileLoadedEvent.target.result;
 this.csvContent = textFromFileLoaded;
 // alert(textFromFileLoaded);

 const txt = textFromFileLoaded;
 const csv = [];
 const lines = txt.split('\n');
 lines.forEach(element => {
   const cols: string[] = element.split(csvSeparator);
   csv.push(cols);
 });
 this.parsedCsv = csv;
 // console.log(this.parsedCsv);
}

Now you have the parsed CSV with its lines and columns in the two-dimensional array parsedCsv (1st dimension is the rows, 2nd dimension the column). You can replace the separator if required - default is semicolon.

Example:

A file containing

A;B;C
1;2,300;3
4;5.5;6

produces the following data structure in parsedCsv

ExampleContent

You can see that if your file contains column headers, then the data starts with row index 1, otherwise (without column headers) with row index 0.

Updated Stackblitz Example

Note: On Stackblitz, I have added the following few lines so you can see how the array is populated:

  // demo output as alert
  var output: string="";
  csv.forEach(row => {
    output += "\n";
    var colNo = 0;
    row.forEach(col => {
      if (colNo>0) output += " | ";
      output += col;
      colNo++;
    });
  });
  alert(output);
Matt
  • 25,467
  • 18
  • 120
  • 187
  • Thank you that was very helpful. But I'm having an issue inside the onFileLoad function: when I pass in a method in the outer class the debugger says that the "this" is bound to the FileReader class... – lmngn23 Jun 12 '20 at 16:27
  • @user545871 - can you create the issue you have as a stackblitz example? That makes it easier to understand and resolve it. – Matt Jun 13 '20 at 07:34
4
csv2Array(fileInput: any){
//read file from input
this.fileReaded = fileInput.target.files[0];

let reader: FileReader = new FileReader();
reader.readAsText(this.fileReaded);

reader.onload = (e) => {
  let csv: string = reader.result;
  let allTextLines = csv.split(/\r|\n|\r/);
  let headers = allTextLines[0].split(',');
  let lines = [];

  for (let i = 0; i < allTextLines.length; i++) {
    // split content based on comma
    let data = allTextLines[i].split(',');
    if (data.length === headers.length) {
      let tarr = [];
      for (let j = 0; j < headers.length; j++) {
        tarr.push(data[j]);
      }

      // log each row to see output 
      console.log(tarr);
      lines.push(tarr);
    }
  }
  // all rows in the csv file 
  console.log(">>>>>>>>>>>>>>>>>", lines);
} }
Mahendra Waykos
  • 666
  • 2
  • 7
  • 18
  • ty for the example. csv.split(/\r|\n|\r/); makes from my csv file an extra empty value at each row. I also see split(',') can make me troubles, as I am at Europe, and we write 19,456 for a float number at an excel file. This migth be solved making the correspondant replace(",",".") before it. –  Nov 07 '17 at 09:01
  • to handle error Type 'string | ArrayBuffer' is not assignable to type 'string'..... do type casting const csv: string = reader.result as string; – Hemant Raghuwanshi Dec 12 '18 at 06:37
0

I found a way to extract the text:

    var fr = new FileReader();
    fr.onload = function(e) {
      var text = fr.result;
      console.log(text);
    };
    fr.readAsText(files[0]);

And then using split separate rows and cells into arrays:

var rows = text.split("\n");
console.log("Row 0 " + rows[0]);
console.log("Row 1 " + rows[1]);
console.log("Row 2 " + rows[2]);
var row1 = rows[0].split(";");
console.log("Value 0,0 " + row1[0]);
console.log("Value 0,1 " + row1[1]);
console.log("Value 0,2 " + row1[2]);

*This code doesn't work well if cells contain ";" characters, wich is not the case of my application.

0

I wrote a function as a Promise

  /**
   * Returns the CSV file content as a string.
   * New lines are separated by '\n' inside the string.
   * @param file 
   */
  public async readCSVFile(file: File): Promise<string | ArrayBuffer> {
    return new Promise<string | ArrayBuffer>((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = e => {
        return resolve((e.target as FileReader).result);
      };
      reader.onerror = e => {
        console.error(`FileReader failed on file ${file.name}.`);
        return reject(null);
      };

      if (!file) {
        console.error('No file to read.');
        return reject(null);
      }
      reader.readAsText(file);
    });
  }

You can use it like this

this.readCSVFile(myCsvFile).then((fileContents) => {
      //fileContents --> CSV file content as a string
 });

And, if you are fighting with spanish characters like ñ, this worked for me

reader.readAsText(file, 'ISO-8859-3');
Franco Dipre
  • 371
  • 3
  • 9