Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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);
like image 716
Botond Béres Avatar asked Jul 19 '19 15:07

Botond Béres


1 Answers

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]
    }
    ...
]
...
like image 61
Elias Garcia Avatar answered Oct 31 '22 07:10

Elias Garcia