Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using TranslateModule.forChild() in an Angular lib #883

Open
matheuscaldasrj opened this issue Jun 28, 2018 · 22 comments
Open

Using TranslateModule.forChild() in an Angular lib #883

matheuscaldasrj opened this issue Jun 28, 2018 · 22 comments

Comments

@matheuscaldasrj
Copy link

matheuscaldasrj commented Jun 28, 2018

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:

@UrsBeeli
Copy link

We have the exact same problem.

@Yogu
Copy link

Yogu commented Sep 14, 2018

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: #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.

@kshitij-tf-zz
Copy link

@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.

@tlaskowski
Copy link

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?

@segux
Copy link

segux commented Nov 5, 2018

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();
  }

@juanjinario
Copy link

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.

@animbalk
Copy link

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 { }

@kurtiev
Copy link

kurtiev commented Apr 1, 2019

@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] } })

@greetclock
Copy link
Contributor

greetclock commented May 8, 2019

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.

@PedroMGMendes
Copy link

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..

@JMuhire
Copy link

JMuhire commented Sep 17, 2019

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));
	}
}

@ahmadmu
Copy link

ahmadmu commented Oct 14, 2019

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"
     ],

@jielimanyili
Copy link

jielimanyili commented Jan 11, 2020

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.

@olofens
Copy link

olofens commented Oct 15, 2020

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.

@Sherpard
Copy link

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

@BdDragos
Copy link

BdDragos commented Jun 3, 2021

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.

@eulersson
Copy link

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

@kacperczerwinski
Copy link

kacperczerwinski commented Feb 4, 2022

@ahmadmu
You saved my day! Thanks

@morbargig
Copy link

morbargig commented Apr 2, 2023

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 :)

@IJustDev
Copy link

IJustDev commented Nov 2, 2023

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
Copy link

IJustDev commented Nov 2, 2023

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 -->

@allexgut
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests