Angular: Return Observable / ES6 Promise from FileReader

*爱你&永不变心* 提交于 2019-11-30 03:12:14

问题


I was trying to return result from FileReader and I found this implementation. But since it is outdated, I'm wondering how to implement the same using ES6 Promises or Rx Observables.

Below is my code with reference to the aforementioned link and it works as expected.

import { Injectable } from '@angular/core';
import * as XLSX from 'xlsx';
import * as XLS from 'xlsx';

@Injectable()
export class ExcelReaderService {

  constructor() { }

  importFromExcel(ev): JQueryPromise<any> {
    let deferred = $.Deferred();

    let regex = /^([a-zA-Z0-9\s_\\.\-:])+(.xlsx|.xls)$/;

    let workbook;
    let excelInJSON;

    if (regex.test(ev.target.files[0].name.toString().toLowerCase())) {
      let xlsxflag = false; /*Flag for checking whether excel is .xls format or .xlsx format*/
      if (ev.target.files[0].name.toString().toLowerCase().indexOf(".xlsx") > 0) {
        xlsxflag = true;
      }

      let fileReader = new FileReader();

      fileReader.onload = (ev) => {
        let binary = "";
        let bytes = new Uint8Array((<any>ev.target).result);
        let length = bytes.byteLength;
        for (let i = 0; i < length; i++) {
          binary += String.fromCharCode(bytes[i]);
        }

        /*Converts the excel data in to json*/
        if (xlsxflag) {
          workbook = XLSX.read(binary, { type: 'binary', cellDates: true, cellStyles: true });
          // only first sheet
          excelInJSON = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
          deferred.resolve(excelInJSON);
        }
        else {
          workbook = XLS.read(binary, { type: 'binary', cellDates: true, cellStyles: true });
          excelInJSON = <{}[]>XLS.utils.sheet_to_row_object_array(workbook.Sheets[workbook.SheetNames[0]]);
          deferred.resolve(excelInJSON);
        }
      }

      // init read
      if (xlsxflag)
        fileReader.readAsArrayBuffer((<any>ev.target).files[0]);
      else
        fileReader.readAsBinaryString((<any>ev.target).files[0]);
    } else {
      deferred.reject('Invalid file!');
    }
    return deferred.promise();
  }

}

In the consumer component

this.excelReaderService.importFromExcel(ev).then((result) => {
    this.detailHeadings = Object.keys(result[0]);
    this.detailData = result;
})

It'll be great if someone helps me with this as I'm new to asynchronous programming.


回答1:


As it's shown in similar case, in order to avoid deferred (anti)pattern FileReader load event should be promisified first:

  let fileReader = new FileReader();
  const fileReaderPromise = new Promise(resolve => fileReader.onload = resolve);

  if (xlsxflag)
    fileReader.readAsArrayBuffer((<any>ev.target).files[0]);
  else
    fileReader.readAsBinaryString((<any>ev.target).files[0]);

  return fileReaderPromise.then(e => {
    let excelInJSON;
    ...
    return excelInJSON;
  });

It can also be converted to an observable with fromEvent:

 const fileReader$ = Observable.fromEvent(fileReader, 'load')
 .map(e => ...)
 .first();

 if (xlsxflag)
    fileReader.readAsArrayBuffer((<any>ev.target).files[0]);
  else
    fileReader.readAsBinaryString((<any>ev.target).files[0]);
    ...

  return fileReader$;



回答2:


This is how I did it, in case anyone wants an Angular service that reads Excel files and responds with an observable of the content as JSON.

I'm using SheetJS for reading the file and outputting JSON.

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import * as XLSX from 'xlsx';

@Injectable()
export class ExcelReaderService {

  constructor() { }

  importFromExcel(ev): Observable<any> {
    let workbook;
    let excelInJSON;

    const fileReader = new FileReader();

    // init read
    fileReader.readAsArrayBuffer((<any>ev.target).files[0]);

    return Observable.create((observer: Subscriber<any[]>): void => {
      // if success
      fileReader.onload = ((ev: ProgressEvent): void => {
        let binary = "";
        let bytes = new Uint8Array((<any>ev.target).result);
        let length = bytes.byteLength;
        for (let i = 0; i < length; i++) {
          binary += String.fromCharCode(bytes[i]);
        }

        // Converts the excel data in to json
        workbook = XLSX.read(binary, { type: 'binary', cellDates: true, cellStyles: true });
        // only first sheet
        excelInJSON = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);

        observer.next(excelInJSON);
        observer.complete();
      } 

      // if failed
      fileReader.onerror = (error: FileReaderProgressEvent): void => {
        observer.error(error);
      }
    });
  }

}

From the component, just pass the event to this service as shown below and it will respond with the JSON.

this.excelReaderService.importFromExcel(ev)
  .subscribe((response: any[]): void => {
    // do something with the response
  });



回答3:


Improving upon @karthikaruna's answer above, I'd add with Observables, it's easy to make a .pipe chain return what you want from a File object:

(please note that these types might not be 100% correct).

import { Injectable } from '@angular/core';
import { Observable, Subscriber } from 'rxjs';
import { map } from 'rxjs/operators';
import * as XLSX from 'xlsx';

@Injectable()
export class ExcelReaderService {

    public importFromExcel(ev): Observable<any[]> {
        return this.fileToString(ev.target.files[0])
            .pipe(
                // convert from file contents to Excel rows
                map((binary: string): any[] => {
                    // Converts the excel data in to json
                    const workbook = XLSX.read(binary, { type: 'binary', cellDates: true, cellStyles: true });
                    // only first sheet
                    const excelInJSON = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
                    return excelInJSON;
                }),
            );
    } // end importFromExcel()

    private fileToString(file: File): Observable<string> {
        return Observable.create(
            (sub: Subscriber<string>): void => {
                const r = new FileReader;
                // if success
                r.onload = (ev: ProgressEvent): void => {
                    sub.next((ev.target as any).result);
                };
                // if failed
                r.onerror = (ev: FileReaderProgressEvent): void => {
                    sub.error(ev);
                };
                r.readAsText(file);
            }
        );
    } // end fileToString()

}


来源:https://stackoverflow.com/questions/46513123/angular-return-observable-es6-promise-from-filereader

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!