http-loader icon indicating copy to clipboard operation
http-loader copied to clipboard

Multiple HTTP requests

Open SoapyMan opened this issue 5 years ago • 6 comments

Current behavior

Currently TranslateHttpLoader produces HTTP request every time getTranslation is called.

Expected behavior

TranslateHttpLoader should store state of language file loading and only one HTTP request is made.

How do you think that we should fix this?

This is how I implemented this (proof of concept, it performs only ONE request, everything works and tokens are translated):

	private errorState = false;
	private waiter = new Subject<Object>();
	private requestedLang: string;

    getTranslation(lang: string): Observable<Object>
	{
		if(this.errorState)
			return of({});

		if(this.requestedLang != null && this.requestedLang == lang)
			return this.waiter.asObservable();

		let languagPath = `assets/locale/${lang}.json`;

		this.requestedLang = lang;

		this.http.get(languagPath).pipe(
				catchError(error => {
					console.warn("can't load language file", languagPath, error);
					this.errorState = true;
					return of({});
				})
			).subscribe(result => {
				//console.log("translation", lang,"loaded!");
				this.waiter.next(result);
			});

		return this.waiter.asObservable();
	}

Minimal reproduction of the problem with instructions

Even standard ngx-translate template calls getTranslation at least two times. My app got approximately 140 (app with multiple pages and modules) and lots of annoying alerts were displayed in case when translations were not found. If success, other requests fetched (304), but fetch still has delay.

Environment


ngx-translate version: 11.0.1
Angular version: 7.1.4

Browser:
- [x] Chrome (desktop) version XX

 
For Tooling issues:
- Node version: 10.15
- Platform:  Windows

SoapyMan avatar Sep 20 '19 12:09 SoapyMan

I'm experiencing the same issue. The json file with i18n translations is requested multiple times. It seems to be requested in an infinite loop It only occurs with non default language files (= languages that have not been set with setDefaultLang())

See https://cl.ly/f5b0f57313a8

emeryowa avatar Sep 30 '19 14:09 emeryowa

@emeryowa this is indeed not well behaviour. In fact now i'm not using http-loader due to this issue. This TranslateHttpLoader implementation itself is too trivial to be added as package to your project IMO. Instead, it's better to implement your own TranslateLoader with the getTranslation function i've posted in the issue.

SoapyMan avatar Oct 01 '19 06:10 SoapyMan

@SoapyMan I have implemented a custom loader, identical to the function you posted. I am getting the same error though = the call to the translations file is requested multiple times. Do you have any tips?

emeryowa avatar Oct 21 '19 15:10 emeryowa

@emeryowa @SoapyMan

I've encountered the same error. Fortunately, the error message with your loader @SoapyMan is much better than the one in the ngx-translate loader.

My Error shows:

     my-translate-loader.ts:31 can't load language file assets/i18n/en.json TypeError: Cannot read property 'length' of undefined
    at HttpHeaders.push../node_modules/@angular/common/fesm5/http.js.HttpHeaders.applyUpdate (http.js:244)
    at http.js:215
    at Array.forEach (<anonymous>)
    at HttpHeaders.push../node_modules/@angular/common/fesm5/http.js.HttpHeaders.init (http.js:215)
    at HttpHeaders.push../node_modules/@angular/common/fesm5/http.js.HttpHeaders.forEach (http.js:280)
    at Observable._subscribe (http.js:1596)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable._trySubscribe (Observable.js:43)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:29)
    at subscribeToResult (subscribeToResult.js:13)
    at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._innerSub (mergeMap.js:74)

bjesuiter avatar Dec 09 '19 22:12 bjesuiter

I created the following loader which does simple caching:

import {HttpClient} from "@angular/common/http";
import {TranslateLoader} from "@ngx-translate/core";
import {Observable} from "rxjs";
import {shareReplay} from "rxjs/operators";

export class CachedHttpTranslationLoader implements TranslateLoader {
  
    cache$: Observable<Object> = null;
    cachedLang: string = null;
    
    constructor(private http: HttpClient, public prefix: string = "/assets/i18n/", public suffix: string = ".json") {}
  
    /**
     * Gets the translations from the server
     */
    public getTranslation(lang: string): Observable<Object> {
      if (!this.cache$ || this.cachedLang !== lang) {
        this.cache$ = this.http.get(`${this.prefix}${lang}${this.suffix}`).pipe(shareReplay(1));
        this.cachedLang = lang;
      }
      return this.cache$;
    }
  }

larsvliet avatar Apr 08 '20 20:04 larsvliet

@larsvliet thank you It works very well

 TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useClass: CachedHttpTranslationLoader,
        deps: [HttpClient]
      },
      defaultLanguage: 'ko' 
    }),

mwmw7 avatar Sep 06 '21 16:09 mwmw7