question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Problem with setting translocoConfig using httpcall in factory, when TranslocoService as dependency anywhere

See original GitHub issue

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

After migrating from ngx-translate, transloco can’t load translocoConfig using factory, as the config object needs to be populated by data from ConfigService (ngx-config), which loads the config file loaded by http call, if the TranslocoService is dependency in any module or provided service, e.g. HttpInterceptor.

I know this is very mouthful so please check the minimal reproduction example app below.

Expected behavior

We expect to be able to get values from ConfigService in customTranslateConfigFactory, just as when using ngx-translate, as the configuration of our app has not changed.

Minimal reproduction of the problem with instructions

Please check the code (you can install and serve the app directly, using docker is not mandatory) in minimal reproduction app and let me explain:

The tech stack in this app is Transloco (migrated from ngx-translate mainly to support Ivy) and ngx-config.

There are slovak and english translation files, identified by lang identifier (sk or en) in the name of the file. The lang identifiers are to be loaded from config file and are replaced in url in HttpInterceptor. Both config and translation json files are loaded by http calls, hosted on cdn. All the logic is currently in app.module.ts:

for config file:

...
export function configFactory(http: HttpClient): ConfigLoader {
  return new ConfigHttpLoader(http, 'http://cdn.pelikan.sk/app/transloco-minimal-example/market-config.json');
}
...
ConfigModule.forRoot({
  provide: ConfigLoader,
  useFactory: (configFactory),
  deps: [HttpClient]
}),
...

for transaltions:



@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoader implements TranslocoLoader {
  constructor(private readonly http: HttpClient) {}

  private readonly i18nUrl: string = `http://cdn.pelikan.sk/app/transloco-minimal-example/{lang}-translations.json`;

  getTranslation(): Observable<any> {
    return this.http.get<Translation>(this.i18nUrl);
  }
}
...
providers: [
...
  { provide: TRANSLOCO_LOADER, useClass: TranslocoHttpLoader, deps: [HttpClient] },
...
]
...

all good so far, both files are downloaded, also in correct order. We try to create translocoConfig using factory, as we need values from ConfigService.


export function customTranslateConfigFactory(config: ConfigService): TranslocoConfig {
  const marketLangs: ReadonlyArray<any> = config.getSettings<ReadonlyArray<any>>('market.languages', []);
  const availableLangs: Array<string> = marketLangs.map((language: any) => language.code);
  const defaultLang: string = marketLangs.reduce((prev: string, curr: any) => curr.default ? curr.code : prev, '');

  return translocoConfig({
    defaultLang,
    availableLangs,
    failedRetries        : 0,
    reRenderOnLangChange : true,
    fallbackLang         : defaultLang,
    prodMode             : environment.production
  });
}
...
providers: [
...
  { provide: TRANSLOCO_CONFIG, useFactory: (customTranslateConfigFactory), deps: [ConfigService] },
...
]
...

Now… This setup works! But only as far as the TranslocoService is not used as dependency in anywhere. In this app, it is used as dependency for CustomHttpInterceptor, where we want to set this.translate.getActiveLang() as X-App-Language request header. Please note that this use-case is only for example of the behavior, in the enterprise app we want to use injected TranslocoService in many places, e.g. in ngrx Effects etc.

So when TranslocoService is used as dependency of HttpModule (i guess it is as it’s used in HttpInterceptor), translocoConfig does not have the ConfigService data (as it needs HttpClient to download data) and the factory returns empty config values. It is a kind of circle dependency problem.

In current state the app works, because the values that have not been able to be set in module are set imperatively in app.component.ts (ConfigService returns correct config data in component):


const marketCode: string = this.config.getSettings('market.code', '');
    const languages: ReadonlyArray<string> = this.config.getSettings('market.languages', []).map((language: any) => language.code);
    const defaultLang: string = this.config.getSettings('market.languages', [])
      .reduce((prev: string, curr: any) => curr.default ? curr.code : prev, '');

    const availableLangs = this.config.getSettings('market.languages', [])
      .map((language: any) => language.code);

    this.translate.setAvailableLangs(availableLangs);
    this.translate.setDefaultLang(defaultLang);
    this.translate.setActiveLang(defaultLang);

But this is not the behavior we prefer (setting imperatively). The app also works if we comment out imperative setting mentioned above, and we remove TranslocoService from CustomHttpInterceptor deps (app.module.ts — line 85)

What is the motivation / use case for changing the behavior?

Please note that even when this seems to be ngx-config issue at first sight, I really believe it has to do with Transloco too, so please don’t shove it off the table right away.

Before, when using ngx-translate, the whole config/translate initialization process was the same, except the class provided for TranslateModule looks little bit different:

in ngx-translate, we use TranslateHttpLoader class to load the data:


export class BrowserTranslateLoader implements TranslateLoader {
  private readonly _browserLoader: TranslateHttpLoader;

  constructor(
    private readonly http   : HttpClient
  ) {
    this._browserLoader = new TranslateHttpLoader(http, http://cdn.pelikan.sk/app/transloco-minimal-example/{lang}-translations.json);
  }

  getTranslation(languageCode: string): Observable<any> {
    return this._browserLoader.getTranslation(languageCode);
  }
}

in Transloco, we load the translation file directly with HttpClient:


@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoader implements TranslocoLoader {
  constructor(private readonly http: HttpClient) {}

  private readonly i18nUrl: string = `http://cdn.pelikan.sk/app/transloco-minimal-example/{lang}-translations.json`;

  getTranslation(): Observable<any> {
    return this.http.get<Translation>(this.i18nUrl);
  }
}

and this is the only difference.

Is there any way to set up this whole config & translation loading process in AppModule only, as with ngx-translate? Or is this simply impossible by design and the config values must be set imperatively from component?

Thanks for any suggestions!

Environment


Angular version: 9.04


Browser: any
 
For Tooling issues:
- Node version: any
- Platform:  any

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:11 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
ArielGuetacommented, Mar 9, 2020

You have a race condition. customTranslateConfigFactory function isn’t going to wait until configFactory request is back.

0reactions
ArielGuetacommented, Apr 25, 2020

Because based on your code, I can’t see any difference with the loaders. TranslocoHttpLoader and BrowserTranslateLoader are both returns the same results.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Config Options | Transloco Angular i18n - GitHub Pages
fallbackLang ​. Sets the default language/s to use as a fallback. See the TranslocoFallbackStrategy section if you need to customize it: translocoConfig({
Read more >
Lazy Load and Encapsulate i18n Files in Angular with Transloco
The first option we have is to load the translation files from the server. We could leverage the inline loader, by providing a...
Read more >
Guide to Angular localization with Transloco examples
You can display a custom template while your translations are loading. Using different languages simultaneously. You may force the different ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found