core
core copied to clipboard
Using TranslateModule.forChild() in an Angular lib
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:
We have the exact same problem.
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.
@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.
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?
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();
}
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.
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 { }
@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] } })
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.
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
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..
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));
}
}
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"
],
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.
Besides adding the standard
TranslateModule.forRoot(...)
in myAppModule
, I simply import and export theTranslateModule
(no .forChild()) in mySharedModule
worked for me also, surprisingly. Before that, I gottranslate
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.
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
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
innode_modules
and inprojects/<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 myAppModule
of the tester app andTranslateModule
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.
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
@ahmadmu You saved my day! Thanks
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 :)
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
I also do in the child module export: [TranslateModule]
In the app module.ts I do for root
But somehow nothing is resolved. Any ideas?
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 -->
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.