0

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?

4
  • I think you're making it a bit too complex. Is it OK if I write you up an answer that stores the list of contents inside a Service? That way you can access it from anywhere in your code. (I am assuming that your readFolder function works) Commented Sep 24, 2019 at 8:12
  • that's alright, i was thinking that it's a bit complex but i am really inexperienced so i wasn't sure if there could be an easier way Commented Sep 24, 2019 at 8:13
  • the only thing i can really say it works is the console.log in the readFile function, it perfectly displays the array i want to have access to Commented Sep 24, 2019 at 8:14
  • In the end you didn't really overcomplicate it, you were pretty close to a working solution. Commented Sep 24, 2019 at 8:38

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks! I'll try this solution and hope it works. One beginner fault i probably also do is to try to put everything in one component instead of implementing different services which seems much more useful on the grander scheme...
Hi! I am getting several errors with this service: codesandbox.io/s/angular-x5j9n (property) providedIn: string Expected 0 arguments, but got 1., Cannot find name 'Subject', Property 'files' does not exist on type 'ReadFolderService'.
@J.Doe you seem to have forgotten the Subject import at the top of read-folder.service.ts
So, I made a few more errors than I expected, but here is a fixed example of your code codesandbox.io/s/angular-m86uk

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.