-
Notifications
You must be signed in to change notification settings - Fork 20
Loading Translations via APP_INITIALIZER
FilipLeitner edited this page Oct 10, 2024
·
2 revisions
Using APP_INITIALIZER allows us to perform tasks before the app is fully initialized. In this case, we're using it to load both configuration and translations, ensuring they're available as soon as the app starts.
Create a service to handle the loading of configurations and translations. Here's a simplified version based on the actual implementation:
import { Injectable, Injector, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { BehaviorSubject, Observable, forkJoin, of } from "rxjs";
import { catchError, tap, switchMap, filter, take } from "rxjs/operators";
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
import { HsConfig, HsLanguageService, WebpackTranslateLoader } from "hslayers-ng/services/language";
@Injectable({
providedIn: "root",
})
export class AppConfigService {
private configSubject = new BehaviorSubject<AppConfig | undefined>(undefined);
private injector = inject(Injector);
readonly assetsPath = `assets/`;
readonly config$ = this.configSubject.asObservable().pipe(
filter((config): config is AppConfig => !!config)
);
constructor(
private http: HttpClient,
private hsConfig: HsConfig,
private hsLanguageService: HsLanguageService
) {}
initializeConfig() {
const configURL = `${this.assetsPath}config.json`;
const config$ = this.http.get<AppConfig>(configURL).pipe(
catchError(() => of({ /* default config */ } as AppConfig))
);
const translations$ = this.getConfigFile("translations");
forkJoin([config$, translations$])
.pipe(
tap(([config, translationOverrides]) => {
// Update config and set translation overrides
this.updateConfig(config, translationOverrides);
}),
switchMap(([config, _]) => this.waitForTranslationsToLoad(config)),
switchMap(() => this.fetchLayersAndLocations(config))
)
.subscribe({
/* Passing value to configSubject which 'starts' the application */
next: (finalConfig) => this.configSubject.next(finalConfig),
error: (error) => {
console.error("An error occurred while initializing the configuration:", error);
this.configSubject.next(/* default config */);
},
});
}
private updateConfig(config: AppConfig, translationOverrides: any) {
// Update HSLayers config
this.hsConfig.update({ ...this.baseConfig, ...config.hslayersConfig, translationOverrides });
}
private waitForTranslationsToLoad(config: AppConfig): Observable<boolean> {
const currentLoader = this.hsLanguageService.getTranslator().currentLoader as WebpackTranslateLoader;
const loadedLanguages$ = toObservable(currentLoader.loadedLanguages);
return loadedLanguages$.pipe(
filter((loadedLanguages) => loadedLanguages[config.hslayersConfig.language || "en"]),
take(1),
tap((loadedLanguages) => {
//Mark loaded lanaguages as loaded via the initializer to prevent unnecessary reloads
//of the translations. Reload may cause the translations to not be available on first compoonent paint
currentLoader.loadedViaInitializator.push(...Object.keys(loadedLanguages));
currentLoader.loadedViaInitializator.push(...Object.keys(loadedLanguages));
})
);
}
}
Set up the APP_INITIALIZER in a module:
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { AppConfig} from './aiConfig.service';
@NgModule({
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: (config: AppConfigService) => {
return () => {
config.initializeConfig();
return config.config$.pipe(take(1));
};
},
deps: [AppConfigService],
},
],
})
export class InitializerModule {}
- The
initializeConfig
method inAppConfigService
is called during app initialization. - It uses
forkJoin
to load both the main configuration and translations simultaneously:forkJoin([config$, translations$])
- After loading, it updates the app configuration and sets the translation overrides:
tap(([config, translationOverrides]) => { this.updateConfig(config, translationOverrides); })
- It then waits for the translations to be fully loaded and marks loaded langugages as
loadedViaInitializator
which is cructial. This step prevents additional reload of the translation which is normally used to add translationOverridesswitchMap(([config, _]) => this.waitForTranslationsToLoad(config))
- Once translations are loaded, it fetches additional configurations (layers and locations):
switchMap(() => this.fetchLayersAndLocations(config))
- Finally, it emits the complete configuration:
next: (finalConfig) => this.configSubject.next(finalConfig)
Quick Links: Home ➖ App configuration ➖ Layer configuration ➖ Cesium configuration ➖ Composition schema (separate repo)