core icon indicating copy to clipboard operation
core copied to clipboard

Using TranslateModule.forChild() in an Angular lib

Open matheuscaldasrj opened this issue 6 years ago • 22 comments

Current behavior

I am trying to use ngx-translate inside an Angular 5 lib.

I have read from docs that "The forRoot static method is a convention that provides and configures services at the same time. Make sure you only call this method in the root module of your application, most of the time called AppModule"

So I thought that I should use Translate.forRoot() in my application and Translate.forChild() in my lib module

The problem is that when I use forRoot in my App and forChild in my lib I always get the following error:

ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[TranslateService -> TranslateStore]: 
  StaticInjectorError(Platform: core)[TranslateService -> TranslateStore]: 
    NullInjectorError: No provider for TranslateStore!
Error: StaticInjectorError(AppModule)[TranslateService -> TranslateStore]: 
  StaticInjectorError(Platform: core)[TranslateService -> TranslateStore]: 
    NullInjectorError: No provider for TranslateStore!

I have tested and it works when I use forRoot in application and lib, but I have to use this.translate.use("myLanguage") because seems I have two instances of TranslateService and this doesn't seems to be the proper way.

Expected behavior

My lib should work using Translate.forChild({}) and my application using Translate.forRoot({})

How do you think that we should fix this?

Minimal reproduction of the problem with instructions

Application module

export function createTranslateLoader(http: HttpClient) { 
  return new TranslateHttpLoader(http, './assets/i18n/', '.json'); 
} 
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient],
      }
    })  
  ],
  providers: [MyService],
  bootstrap: [AppComponent]
})

Lib module

export function createTranslateLoader(http: HttpClient) { 
  return new TranslateHttpLoader(http, './assets/my-other-path/i18n/', '.json'); 
} 


@NgModule({
  imports: [
    NoopAnimationsModule,
    CommonModule
    TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient],
        
      }
    })
  ],
  declarations: [
    MessageButtonComponent,
  ],
  exports: [
    MessageButtonComponent
  ],
  providers: [
    TranslateService
  ],
  entryComponents: [

  ]
})

Environment


ngx-translate version: 9.1.1
Angular version:  core 5.2.0 


Browser:
- [x] Chrome (desktop) version 67
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: v8.11.2 
- Platform:  Windows 10

Others:

matheuscaldasrj avatar Jun 28 '18 12:06 matheuscaldasrj

We have the exact same problem.

UrsBeeli avatar Jul 11 '18 13:07 UrsBeeli

I think you should just import TranslateModule directly to get access to the directives and pipes, and leave it up to the app to provide the services. Having multiple loaders is not really supported anyway: https://github.com/ngx-translate/core/issues/763

See the angular documentation on what to import:

What should I import?

Import NgModules whose public (exported) declarable classes you need to reference in this module's component templates.

Note: nothing mentioned of services

How do I restrict service scope to a module?

[...] As a general rule, import modules with providers exactly once, preferably in the application's root module. That's also usually the best place to configure, wrap, and override them.

They also distinguish between Widget modules and Service modules.

If I understand the usage section of the README of ngx-translate correctly, they also recommend importing TranslateModule in shared modules:

Note: Never call a forRoot static method in the SharedModule. You might end up with different instances of the service in your injector tree. But you can use forChild if necessary.

Note that it says use forChild if necessary, not as a general rule for shared modules.

Maybe we should document this more explicitly in the README.

Yogu avatar Sep 14 '18 09:09 Yogu

@matheuscaldasrj - Could you find any approach to achieve this behaviour? I am facing similar issue where language change event on application translate service is not propagating to the library.

kshitij-tf-zz avatar Sep 20 '18 10:09 kshitij-tf-zz

Hi I have similar error. I'm using:

"@ngx-translate/core": "11.0.0", "@ngx-translate/http-loader": "4.0.0",

App.module.ts:

TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: TranslationProviders,
        deps: [HttpClient],
      },
    }),

My LazyLoaded module Imports SharedModule which imports and exports TranslateModule. I tried to import TranslateModule with forChild but didn't help.

Any idea?

tlaskowski avatar Nov 02 '18 14:11 tlaskowski

Hi, i wrote this ugly solution invoking setTranslation with TranslateService and appending, all translations are come to be loaded and run everything fine:

This is my finally solution

Library code

availableLanguages.ts

import { en } from './en.ts';
import { es } from './es.ts';
export const languages = { en, es };


translations.service.ts

@Injectable()
export class LibTranslationsService {
  private availableLanguages = languages;
  constructor(private translate: TranslateService) {
  }

  init(): any {
    Object.keys(this.availableLanguages).forEach((language) => {

     // Timeout because i need to be async calls after init app
      setTimeout(() => this.translate.setTranslation(language, this.availableLanguages[language], true));
    });

  }

  getTranslations() {
    return this.availableLanguages;
  }
}

App

  app.component.ts
  ngOnInit() {
    this.translateUiService.init();
  }

segux avatar Nov 05 '18 08:11 segux

I dont have problems with Translate module, I have in my app.module

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app.routing';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    HttpClientModule,
    SharedModule,
    AppRoutingModule,
    TranslateModule.forRoot({
        loader: {
            provide: TranslateLoader,
            useFactory: HttpLoaderFactory,
            deps: [HttpClient]
        }
    })
  ],
  exports: [],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

// required for AOT compilation
export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http);
}

And In my shared.module I have

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MaterialModule } from '../pages/component/material.module';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ToastrModule } from 'ngx-toastr';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

@NgModule({
  declarations: [],
  imports: [
    RouterModule,
    MaterialModule,
    FlexLayoutModule,
    HttpClientModule,
    ToastrModule.forRoot(),
    TranslateModule.forChild({
      loader: {
          provide: TranslateLoader,
          useFactory: HttpLoaderFactory,
          deps: [HttpClient]
      }
    })
  ],
  exports: [
    MaterialModule,
    FlexLayoutModule,
    ToastrModule,
    TranslateModule
  ]
})
export class SharedModule { }

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http);
}

I think that the problem can be that you didnt put the Translate Module in the exports zone of the Shared Module.

juanjinario avatar Jan 15 '19 08:01 juanjinario

Facing the same issue. I have even tried exporting TranslateModule as well.


import { CoreModule } from './core/core.module'; import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { UpgradeModule } from '@angular/upgrade/static'; import { EventPolicyModule } from './policy/event-policy/event-policy.module'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { HttpClient } from '@angular/common/http'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core';

export function createTranslateLoader1( http: HttpClient){ return new TranslateHttpLoader(http, 'assets/i18n/core/', '.json'); }

@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, UpgradeModule,

CoreModule,
EventPolicyModule,

TranslateModule.forRoot({
  loader : {
    provide : TranslateLoader, 
    useFactory : (createTranslateLoader1),
    deps : [HttpClient]
  }
})

], providers: [], bootstrap: [AppComponent] })

export class AppModule { }

import { AppRoutingModule } from './../app-routing.module'; import { NgModule } from '@angular/core'; import { CommonModule, LocationStrategy, HashLocationStrategy } from '@angular/common'; import { UrlHandlingStrategy, UrlTree } from '@angular/router'; import { HttpClientModule, HttpClient } from '@angular/common/http'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';

export class NgAppUrlHandlingStrategy implements UrlHandlingStrategy { // only process the angular url shouldProcessUrl(url: UrlTree) { return url.toString().startsWith('/configuration/impolicies'); }

extract(url: UrlTree) { return url; }

merge(url: UrlTree, whole: UrlTree) { return url; } }

export function createTranslateLoader2( http: HttpClient){ return new TranslateHttpLoader(http, 'assets/i18n/core/', '.json'); }

@NgModule({ declarations: [], imports: [ CommonModule, HttpClientModule, TranslateModule.forChild({ loader : { provide : TranslateLoader, useFactory : (createTranslateLoader2), deps : [HttpClient] }, isolate : true }) ], exports :[TranslateModule ], providers: [

] })

export class CoreModule { }

animbalk avatar Jan 22 '19 05:01 animbalk

@juanjinario Thanks. Guys, I also had that issue, but after adding:

TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: createTranslateLoader, deps: [HttpClient] } })

into app.module.ts it started work fine.

and in Shared module: TranslateModule.forChild({ useDefaultLang: true, isolate: false, loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient] } })

kurtiev avatar Apr 01 '19 08:04 kurtiev

I had a similar problem as described in the first message in this thread. I tried to create an Angular 7 library with ng g lib. Turned out that I had @ngx-translate/core in node_modules and in projects/<my-lib>/node_modules. Removing the inner folder completely (projects/<my-lib>/node_modules) solved my problem.

After that, I use TranslateModule.forRoot() with no parameters in my AppModule of the tester app and TranslateModule in the imports of my library module.

I hope my message will help somebody.

greetclock avatar May 08 '19 08:05 greetclock

Hi, faced this issue just yesterday with version 11.0.1 and Angular & CLI 8, what worked for me was:

Like state in the documentation in my shared module i had:

TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: (createtranslateloader),
        deps: [HttpClient]
      },
      isolate: true
    }),

and in my AppModule, wich also imports the shared module , i add to provide the TranslateStore

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    CommonModule,
    SharedModule,
    OAuthModule.forRoot(),
    AppRoutingModule,
    CoreModule.forRoot(),
    UiBlocksModule,
    ThemeModule.forRoot(AppThemes)
  ],
  providers: [
    TranslateStore,
    { provide: OAuthStorage, useValue: localStorage },
    { provide: OAuthLogger, useValue: LoggingService }      
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Not sure if this is supposed to be done this way but this did resolve the original error message "NullInjectorError: No provider for TranslateStore!".

But now everything is translated except the lazy loaded modules. (every component loaded inside the had missing translations). I then notice there were at least two instances of the TranslateService, the one create for components outside of the router outlet, and another one for the lazy loaded modules.

What fixed this was setting the isolate: false when registering the translate module in my SharedModule.

This issue ultimately seems to depend a lot with the way the angular project and module registration is set up. In my case the SharedModule and the AppModule do share the same i18n files, but i do realize that if i want to get different i18n files for the SharedModule and the AppModule it does make sense to have different instances of the TranslateService and would have to register the the TranslateModule.forRoot in the AppModule and keep the flag isolate: true in my Shared Module

Hope this can help..

PedroMGMendes avatar Jun 14 '19 09:06 PedroMGMendes

I had the same issue when lazy loading many translated angular 8 modules with ngx-translate/core v.11.0.1 .

In the translation module I have :

@NgModule({
	declarations: [ MyComponent ],
	imports: [
		CommonModule,
		TranslateModule.forChild({
			loader: {
				provide: TranslateLoader,
				useFactory: MyTranslationLoaderFactory,
				deps: [ HttpClient ]
			},
			isolate: false
		}),
		RouterModule.forChild(MY_ROUTES),
		SharedModule
	]
})
export class MyModule {}

In the container I have to add this line in the ngOnInit to make it work:

export class MyContainer implements OnInit {

	constructor(public translationService: TranslateService) {}

	ngOnInit() {
		this.translate.use(this.translate.store.currentLang);
		this.translate.store.onLangChange.subscribe((lang) => this.translate.use(lang));
	}
}

JMuhire avatar Sep 17 '19 14:09 JMuhire

This worked for me, In tsconfig.ts of the main application add this to paths and restart your application:

     "@ngx-translate/core": [
       "node_modules/@ngx-translate/core"
     ],
     "@ngx-translate/http-loader": [
       "node_modules/@ngx-translate/http-loader"
     ],

ahmadmu avatar Oct 14 '19 14:10 ahmadmu

Besides adding the standard TranslateModule.forRoot(...) in my AppModule, I simply import and export the TranslateModule (no .forChild()) in my SharedModule worked for me also, surprisingly. Before that, I got translate pipe error.

jielimanyili avatar Jan 11 '20 00:01 jielimanyili

Besides adding the standard TranslateModule.forRoot(...) in my AppModule, I simply import and export the TranslateModule (no .forChild()) in my SharedModule worked for me also, surprisingly. Before that, I got translate pipe error.

I can confirm that just dropping all .forChild(), and having one .forRoot({...}) in app.module throughout my application + library did the trick. Seems strange though that you can't call .forChild() on what I see as child modules. Oh well.

olofens avatar Oct 15 '20 15:10 olofens

I've been tampering a bit with the library, and found out a solution,

.forChild forgets to provide a TranslateStore, adding

 providers: [{ provide: TranslateStore }]

to the Shared Module seems to solve the injection issues with no .forRoot() call whatsoever

Sherpard avatar Feb 23 '21 17:02 Sherpard

I had a similar problem as described in the first message in this thread. I tried to create an Angular 7 library with ng g lib. Turned out that I had @ngx-translate/core in node_modules and in projects/<my-lib>/node_modules. Removing the inner folder completely (projects/<my-lib>/node_modules) solved my problem.

After that, I use TranslateModule.forRoot() with no parameters in my AppModule of the tester app and TranslateModule in the imports of my library module.

I hope my message will help somebody.

Thank you so so much. I was having this issue and I've spent a considerable amount of time figuring out why suddenly the library doesn't translate anymore. This solved everything.

BdDragos avatar Jun 03 '21 13:06 BdDragos

For lazy-loaded modules with different translation loaders (loading .json from different files) it seems to be either (in the case of the lazy-loaded):

  • (LazyModule isolate: false, extend: true) React to parent module translation events automatically without having to connect anything, just as they say, but cannot load the lazy loaded specific files.
  • (LazyModule isolate: true, extend: true) We have to propagate changes to parent's translation event changes to the lazy child ourselves, and we can have our specific translations working! But the parent's translation won't work.

It's like I can't blend the two.

I got pretty close though maybe you could have a look and play within StackBlitz: https://stackblitz.com/edit/translations-and-lazy-loading?file=README.md

eulersson avatar Jul 15 '21 12:07 eulersson

@ahmadmu You saved my day! Thanks

kacperczerwinski avatar Feb 04 '22 11:02 kacperczerwinski

with lazy module somehow it's essayer it works fine and for non lazy module such as my FrontBaseClientModule that configure with forChild for exam this one did the job appTranslateService is custom extends for TranslateService + typescript and shit but with basic logic as TranslateService

FrontBaseClientModule it's kinda my shared module so onLangChange just call the service to set the Translation and merge it manually. I stoped to lay on the functionality of the ngx-tranlate and start doing what I can manually Instead of looking for some magic configuration online.

FrontBaseClientModule is a NX angular lib that is the core layout and more off few angular application this is way the separate for modules are so important for project that need to load 200 features (lazy module) it can be excellent to lazy load and import separately translations data by the way FrontBaseClientTranslateLoader is not an http loader it's a lazy junk loader which allows me to force some typescript and validation with the translations data and help with finding bugs on compile time instead of run time

export class FrontBaseClientModule {
  constructor(
    frontBaseClientTranslateLoader: FrontBaseClientTranslateLoader,
    appTranslateService: AppTranslateService
  ) {
    appTranslateService.onLangChange.pipe(take(1)).subscribe((event) => {
      firstValueFrom(
        frontBaseClientTranslateLoader.getTranslation(event.lang as any)
      ).then((res) => {
        appTranslateService.setTranslation(event.lang, res, true);
      });
    });
  }
  }

if any of you find this helpful please let me know :)

morbargig avatar Apr 02 '23 16:04 morbargig

Does this work without lazy loaded but with eager loaded modules?

For example I am currently struggling with a child module that is always imported.

In my child module I do TranslateModule.forChild with isolate false Bildschirmfoto 2023-11-02 um 10 16 11 I also do in the child module export: [TranslateModule]

In the app module.ts I do for root Bildschirmfoto 2023-11-02 um 10 16 44

But somehow nothing is resolved. Any ideas?

IJustDev avatar Nov 02 '23 09:11 IJustDev

So I came up with a salutation that might be useful for somebody. If you have non-lazy-loaded modules and use ngx-core, you can create a TranslateLoader yourself, that holds an array of translation loaders. Those will be asked for translations if one translation could not be translated.

import { Injectable } from "@angular/core";
import { TranslateLoader } from "@ngx-translate/core";
import { Observable, zip } from "rxjs";
import { map } from 'rxjs/operators';

@Injectable()
export class MultiTranslationLoader {
  
  protected readonly translationDefinitions: {usagePrefix: string, translationLoader: TranslateLoader}[] = [];
  
  getTranslation(lang: string): Observable<Object> {
    const loaders = this.translationDefinitions.map(definition => definition.translationLoader.getTranslation(lang));
    return zip(...loaders).pipe(
      map((translationsArray) => {
        return translationsArray.reduce((prev, translation, index) => {
          const translationDefinition = this.translationDefinitions[index];
          
          const translationToAppend = translationDefinition.usagePrefix === undefined ? translation : {[translationDefinition.usagePrefix]: translation};

          return {...prev, ...translationToAppend};
        }, {} as any);
      })
    );
  }

  public registerTranslationLoader(usagePrefix: undefined | string, translationLoader: TranslateLoader) {
    this.translationDefinitions.push({
      usagePrefix,
      translationLoader
    });
  }
}

Usage

// app.module.ts
@NgModule({
  imports: [TranslateModule.forRoot({loader: {provide: TranslateLoader, useClass: MultiTranslationLoader})]
})
class AppModule {
  constructor(@Inject(TranslateLoader) translateLoader: MultiTranslationLoader) {
    translateLoader.registerTranslationLoader(undefined, new FakeTranslationLoader());
  }
}
// child.module.ts
@NgModule({
  imports: [TranslateModule]
})
class ChildModule {
  constructor(@Inject(TranslateLoader) translateLoader: MultiTranslationLoader) {
    translateLoader.registerTranslationLoader('child1', new FakeTranslationLoader());
  }
}
// child.component.html
<h1>{{'child1.translationKey' | translate}}</h1> <!-- 'child1', because we provided it as usagePrefix in our child module. -->
<h1>{{'translationKey' | translate}}</h1> <!-- direct access to the translations object with all translations -->

IJustDev avatar Nov 02 '23 14:11 IJustDev

For anyone facing a similar issue, please notice the note in this section of the documentation. It explicitly says: Note: Never call a forRoot static method in the SharedModule. You might end up with different instances of the service in your injector tree. But you can use forChild if necessary. which I see multiple posters (including me) in this issue doing. Moving the forRoot call to the app.module resolved my issue.

allexgut avatar Feb 15 '24 14:02 allexgut