1

I am creating a loader for @ngx-translate, that loads multiple JSON translation files from a directory based on the selected language.

The way I am currently loading the files is via an index.json file that contains an array with names and extensions for the translation files for that language.

The structure of the translation files is as follows:

- assets
  - i18n
    - index.json <-- Contains an array of translation file names
    - en_US
      - home.json
      - header.json
    - de_DE
      - home.json
      - header.json

An example of the index.json is as follows:

[
  "home.json",
  "header.json"
]

Since the Angular HttpClientModule cannot load the contents of a directory or the names of files in a directory (it can only load single json files) I need to define the names in the index.json.

This also means I have to first load the index.json and then load all the other files in the directory.

In code this means the following:

  1. Load the index.json
  2. Loop over the array of file names
  3. Load every file in a separate request
  4. When all finished combine all the file contents into one object

What I've tried

 public getTranslation(language: string): Observable<any> {
    return this.getIndexFile(language)
      .pipe(
        map((fileNames: string[]) => {
          const promises = [];

          for (const name of fileNames) {
            const path = Translation.replaceUrlPlaceholder(this.path, language);
            promises.push(this.http.get(path + '/' + name + '.json').toPromise());
          }

          return Promise.all(promises);
        }),
      );
  }

So I've tried this with promises, but this obviously doesn't work (since an observable must be returned). Also the solution described here doesn't work for me, since I need to dynamicatlly create an unlimited amount of observables and wait for them to finish before I can start on step 4. (combine all files).

What should be the correct way to do this?


  • Angular 7.1.0
  • RxJS 6.3.3
  • @ngx-translate/core 11.0.1

Update

Stackblitz here: Combining observables

Mr.wiseguy
  • 4,092
  • 10
  • 35
  • 67
  • Please provide a [mcve] reproducing the issue. –  Nov 27 '18 at 09:28
  • @trichetriche, updated with more detailed information. Are you missing anything or does this meet the rules of a stackoverflow post? :) – Mr.wiseguy Nov 27 '18 at 12:43
  • Well a sandbox on https://stackblitz.com would be the best, as it would allow us to simply implement the solutions we offer you. –  Nov 27 '18 at 12:45
  • @trichetriche, added a stackblitz that is reproducing my problem. – Mr.wiseguy Nov 27 '18 at 13:07
  • Sorry, was working on the side. I have [made a stackblitz](https://stackblitz.com/edit/angular-dv4tha?file=src/app/app.component.ts) with clear separation of functions and their usage, feel free to check it out ! –  Nov 27 '18 at 13:33
  • Thanks for the stackblitz! I see that I was on the right track (see my answer), but needed to do some more separation like you did. Could you post this as an answer so that I can accept it? – Mr.wiseguy Nov 27 '18 at 13:40
  • Nah don't worry about that, wait 3 days and mark your own as the one, and use my stackblitz if you want. I'm here to help as much people has I can, but this issue was only for you, as it's just the syntax you didn't quite get. And if you can't see it, I don't really need the reputation, so take advantage of it to gain some :) good luck with your project ! –  Nov 27 '18 at 13:49
  • I'l then mark mine as the correct answer, but I've added your answer as an improvement on mine. And thanks, this problem was quite a pain in the ass. – Mr.wiseguy Nov 27 '18 at 14:13
  • I can feel your pain, I had the same kind of issues when I started. To get a better grip of the concept, I looked at [all operators](https://www.learnrxjs.io/operators/) and their marble diagrams, to see what they were doing. If I can advise you one thing to understand all of this faster, is to manage all of your variables through observables and `async` pipes. You'll be forced to learn operators and their syntax this way ! –  Nov 27 '18 at 14:16

1 Answers1

2

So after searching and trying some more I found the solution to my answer. There where 2 problems:

1. Flatmap vs Map

I was using a map() instead of a flatMap. The difference is that the flatMap will execute when the first observable has finished. This way the subscribe won't get the result until the flatMap observable has finished.

2. Promise.all vs forkJoin

The observable equivalent of Promise.all() is forkJoin(). ForkJoin wil execute all observables in parallel and returns the result of all observables in one array.


The result

So updating the code above will result in the following stackblitz: solution

Or in code:

  public getTranslation(language: string): Observable<any> {
    return this.getIndexFile(language)
      .pipe(
        flatMap((fileNames: string[]) => {
          const observables: Observable<any>[] = [];

          for (const name of fileNames) {
            const path = 'assets/i18n/' + language + '/' + name + '.json';
            observables.push(this.http.get(path));
          }

          // Run all the observables in parallel
          return forkJoin(observables);
        }),
      );

Separation of Concern

My code contains multiple actions in one function which make it hard to test. So that should be separated. @trichetriche made a version which includes separation of concern.

See his Stackblitz for the code: Stackblitz

Mr.wiseguy
  • 4,092
  • 10
  • 35
  • 67