Lazy loading and setting LOCALE_ID at runtime from an API in Angular 8 application on startup
Asked Answered
S

2

18

We have an approach for lazy loading and setting the LOCALE_ID of the Angular application from the API (by loading the user profile data during on startup). This worked fine on Angular 7 but when I've upgraded to Angular 8 it stopped working. When the localeFactory is called (see below) the localeService.getLocale() is undefined, it has not been initialized yet. We initialize it in a SharedResolver which is in a SharedModule which is included in the imports of the AppModule. What is the right approach for doing this in Angular 8? In didn't see anything specific to this in the documentation of changes so I guess it's something indirect. Any input on what approach I should take here? Thanks

Please see relevant code below:

app.module.ts

export function localeFactory(localeService: LocaleService) {
    console.log('locale factory called');
    // This is `undefined` here now for some reason
    console.log(localeService.getLocale());

    return localeService.getLocale() || 'en';
}

...
const APP_LOCALE_ID = {
    provide: LOCALE_ID,
    deps: [LocaleService],
    useFactory: localeFactory
};

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpClientModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        SharedModule.forRoot(), // <- in this module we have the SharedResolver
        AccountModule,
        ApiModule,
        GenesysSubprojectModule
    ],
    declarations: [AppComponent],
    providers: [
        AppRouteGuard,
        BreadcrumbService,
        {
            provide: APP_INITIALIZER,
            useFactory: appInitializerFactory,
            deps: [PlatformLocation, BootstrapService],
            multi: true
        },
        { provide: ErrorHandler, useClass: GlobalErrorHandler },
        AppRouteGuard,
        BreadcrumbService,
        // TODO - This doesn't work anymore!
        APP_LOCALE_ID
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}

shared-resolve.ts

export class SharedResolve implements Resolve<any> {
    ...

    resolve(route: ActivatedRouteSnapshot) {
       ...

        const observables = forkJoin(
            this.authService.getPermissions(),
            this.authService.getCurrentProfileFromApi(),
            this.languageApi.getActiveLanguages(), // TODO - cache
            this.tenantSettingsApi.getLogo(logoLastModificationTime),
            this.dashboardApi.getDashboardSettings(),
            this.reportApi.getReportSettings(),
            this.publicSettingsApi.getSettings(),
            this.tenantSettingsApi.getInitializationSettings()
        ) as Observable<any[]>;

        return observables
            .pipe(
                flatMap(result => {
                    ...
                    const profile: CurrentUserProfileExtEditDto = result[1];
                    ...
                    const languageName =
                        profile.languageName || navigator.language; // browser default language
                    console.log('Set locale to languageName=' + languageName);
                    this.storageService.setLocale(languageName);
                    this.localeService.setLocale(languageName);
Selfdetermination answered 19/7, 2019 at 15:37 Comment(3)
May be an Angular issue, seems to be discussed here github.com/angular/angular/issues/31465Briefing
The issue was fixed in Angular 8.2.1Briefing
But I still experience the problem in angular 9.1.13Tropine
S
10

Yep, that's an Ivy i18n issue in v8.x.x. I've posted it in the Angular repo and has been confirmed by @Ocombe (who is working on Ivy i18n).

If you are in a hurry, as a workaround, you can do this:

As Ivy is searching for a runtime locale, you can simply provide a default language in the AppModule like this and then provide your APP_INITIALIZER:

...
providers: [
    ...
    { provide: LOCALE_ID, useValue: 'en' }
    {
        provide: APP_INITIALIZER,
        useFactory: initializeAppSettings,
        deps: [AppInitializerService],
        multi: true
    }
    ...
]
...

This will first set the default locale and then run your APP_INITIALIZER. Next, in your CoreModule (only loaded once), simply provide the new LOCALE_ID fetched from the backend:

...
providers: [
    ...
    {
        provide: LOCALE_ID,
        useFactory: (localeService: LocaleService) => localeService.getCurrentLocale(),
        deps: [LocaleService]
    }
    ...
]
...
Significancy answered 22/7, 2019 at 11:53 Comment(4)
Thanks, I will give this a try while we wait for a fix. Strange though, does this affect us even if we do not have Ivy enabled? Or does this mean that we may have Ivy enabled inadvertently?Briefing
It shouldn't... i18n with Ivy is still not complete and simply they forgot about adding an if statemente as you can see in this commit referenced at the issue by @Ocombe. As you can see, what are they doing now to fix it is setting a default locale (the same as my workaround) so the app won't break on bootstrap.Significancy
In your example is the locale retrieved inside initializeAppSettings? In my case it's a separate SharedResolver in a SharedModuleBriefing
Yes! initializeAppSettings is a factory that calls an AppInitializerService method, which retrieves the locale for the current logged in user from the backend and stores it in the LocaleService. So, when the getCurrentLocale() method from LocaleService in the CoreModule gets executed it already has the locale available to set the LOCALE_ID injection token.Significancy
C
0

What we do in our project is that we actually have lazyloaded modules for each (lang) mutation or "market". This means you use Angular routing and dependency injection to separate i18n related stuff to <lang>.module.ts so you have one for each and all the "site" related things are handled in site.module.ts which actually holds the application itself

You can see the solution here (yet it does not include the LOCALE_ID provide, but you can easily add it there)

https://stackblitz.com/github/vmasek/angular-typed-translations-demo


I wrote a blog post about this alternative i18n approach and typing the translations. You can read more about it here https://blog.angularindepth.com/angular-typed-translations-29353f0a60bc

Cooksey answered 23/7, 2019 at 14:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.