【问题标题】:Angular: Return Observable / ES6 Promise from FileReaderAngular:从 FileReader 返回 Observable / ES6 Promise
【发布时间】:2018-03-12 19:02:15
【问题描述】:

我试图从FileReader 返回结果,我发现this 实现。但由于它已经过时,我想知道如何使用 ES6 Promises 或 Rx Observables 来实现它。

以下是我参考上述链接的代码,它按预期工作。

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();
  }

}

在消费者component

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

如果有人能帮我解决这个问题,那就太好了,因为我是异步编程的新手。

【问题讨论】:

标签: angular typescript rxjs observable es6-promise


【解决方案1】:

我就是这样做的,以防万一有人想要一个读取 Excel 文件并以 observable 的内容作为 JSON 的响应的 Angular 服务。

我使用SheetJS 读取文件并输出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);
      }
    });
  }

}

component,只需将事件传递给此服务,如下所示,它将以 JSON 响应。

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

【讨论】:

    【解决方案2】:

    改进上面@karthikaruna 的回答,我会添加Observables,很容易让.pipe 链从File 对象返回你想要的:

    (请注意,这些类型可能不是 100% 正确)。

    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()
    
    }
    

    【讨论】:

      【解决方案3】:

      正如similar case 所示,为了避免延迟(反)模式FileReader load 事件应该首先被承诺:

        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;
        });
      

      也可以使用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$;
      

      【讨论】:

      • 对不起,我不明白你的解决方案。我的问题中没有像 sn-p 这样简单的东西吗?只需返回一个promiseobservable,我可以在我注入上述ExcelReaderService 的组件中订阅/消费?
      • sn-p 应该驻留在您发布的 importFromExcel 方法中。您创建 fileReaderPromise 并从那里返回它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-24
      • 2023-01-28
      • 2018-11-25
      • 1970-01-01
      • 2021-05-01
      相关资源
      最近更新 更多