J.Doe
J.Doe

Reputation: 596

Angular: FileReader - Access List from reader

I am trying to read a folder via an input and save the content of each of the individual files containt in the folder in an array.

It is all done with this Input:

<div class="form-group">
        <input type="file" id="file" (change)="accessFiles($event)" webkitdirectory directory multiple >
</div>

And this FileReader:

files = []
readFolder(fileChangeEvent: Event) {
    this.readFile(0, (fileChangeEvent.target as HTMLInputElement).files);
 }

private readFile(index, files) {
     const reader = new FileReader();
     if (index >= files.length ) {
       console.log(this.files);
       return;
     }
     const file = files[index];
     reader.onload = (e: any) => {
       const bin = e.target.result;
       this.files.push(e.target.result);
       this.readFile(index + 1, files);
     };
       reader.readAsBinaryString(file);
}

Now, the console.log in readFile displays the array and it looks fine, the only thing now is that i want to use it later in the code, for that i am trying to return it. I tried something like this:

accessFiles(data){
    const contentList =  this.readDocument(data));
}

which did not work. the output turned out to be undefined

What i also tried was subscribing to the data:

accessFiles(data){
     this.readFolder(data).subscribe(values => {
    }
}

but then i got this error:

Property 'subscribe' does not exist on type 'void'.

Which is why I tried to implement an Observable:

files = []
readFolder(fileChangeEvent: Event) {
    return this.readFile(0, (fileChangeEvent.target as HTMLInputElement).files);
 }

private readFile(index, files) {
    return new Observable<any>(obs => {
     const reader = new FileReader();
     if (index >= files.length ) {
       console.log(this.files);
       obs.next({result: this.files});
       return;
     }
     const file = files[index];
     reader.onload = (e: any) => {
       const bin = e.target.result;
       this.files.push(e.target.result);
       this.readFile(index + 1, files);
     };
       reader.readAsBinaryString(file);
    });
}

But the only Output I got out of this was:

Observable {_isScalar: false, _subscribe: ƒ}

Which I am not really sure what that means...

How can i correctly access a List from the FileReader? or what am I doing wrong?

Upvotes: 0

Views: 455

Answers (1)

Robin De Schepper
Robin De Schepper

Reputation: 6365

Things like this are best done inside of a Service, where you create functions inside of that service to fetch, store and return the files at any time you like. You then inject that Service into the constructors of components or other places where you need this service.

1. Generate the service with Angular CLI

ng generate service read-folder

This will create a file named read-folder.service.ts with a service called ReadFolderService inside, and registers it in your root module.

2. Write the service

The only piece of the puzzle that you were really missing was that the FileReader's onload is asynchronous, and that you can't (or shouldn't) synchronously return a value from an asynchronous operation (you would have to "block" your application while reading the files, which is a bad user experience). Instead you should - as you tried - work with Observables from rxjs Angular's preferred asynchronous framework. You can create a Subject and send the .next(value) to it, and where you are waiting for a reply you can .subscribe(value => function) to it.

import { Injectable } from "@angular/core";
import { Subject } from "rxjs";

export class ReadFolderService {
  private fileCache: string[];
  private folderReader$: Subject<string[]> = new Subject();

  public readFolder(files: string[]) {
    this.fileCache = [];
    this.readFile(0, files);
    return this.folderReader$.asObservable(); // I don't return the subject itself, but a "readonly" Observable.
  }

  private readFile(index, files) {
    const reader = new FileReader();
    if (index >= files.length) {
      this.folderReader$.next(this.fileCache);
      return;
    }
    const file = files[index];
    reader.onload = (e: any) => {
      this.fileCache.push(e.target.result);
      this.readFile(index + 1, files);
    };
    reader.readAsBinaryString(file);
  }
}

3. Inject the service, make the call, and subscribe to its result

Inside of your component:

import { ReadFolderService } from 'src/app/read-folder.service';

...

class MyComponent {

  // Angular will inspect the arguments of your constructor and make the service for you.
  // This is called "dependency injection"
  construct(private folderReader: ReadFolderService) { }

  readFolder(fileChangeEvent: Event) {
     this.folderReader.readFolder(fileChangeEvent.target as HTMLInputElement).files)
                      .subscribe( fileContents => {
                         // Do something
                      });
  }
}

And if you want you can also add another function to the service to retrieve the last read files by using the private fileCache value. But your recursive solution might cause you to access the fileCache while you're in the middle of reading another folder.

Upvotes: 1

Related Questions