libs/ngx-pfe/services/pfe-config-service/config-service.service.ts
Service that provides the pfe configuration.
We need to tell the Angular compiler that the NGX_PFE_FLOW_CONFIGURATION generic Promise token is ok: It doesn't like the generic there, as that could cause issues during runtime (Check runs due to strictMetadataEmit). This will not be required anymore, once the library builds are also switched to ivy.
Properties |
|
Methods |
|
Public getAppConfiguration |
getAppConfiguration()
|
Returns :
Promise<APP_CONFIG_TYPE | undefined>
|
Public getConfig |
getConfig()
|
Get the config
Returns :
Promise<NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>>
|
Public getErrorMessageKey |
getErrorMessageKey()
|
Returns :
Promise<string>
|
Public getPageConfiguration | ||||||
getPageConfiguration(pageID: string | undefined)
|
||||||
Parameters :
Returns :
Promise<PAGE_CONFIG_TYPE | undefined>
|
Public getPageNavigationConfiguration | ||||||
getPageNavigationConfiguration(pageID: string | undefined)
|
||||||
Parameters :
|
Public isAppConfigurationAvailable |
isAppConfigurationAvailable()
|
Returns :
Promise<boolean>
|
Public pfeApplicationConfiguration |
Type : AppConfiguration | undefined
|
Default value : undefined
|
Assign the AppConfiguration Object for quick and easy access |
import { NgxLoggerService } from '@allianz/ngx-logger';
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable, InjectionToken, Optional } from '@angular/core';
import deepmerge from 'deepmerge';
import { Observable, firstValueFrom, from, of } from 'rxjs';
import { catchError, first, map, shareReplay } from 'rxjs/operators';
import { AppConfiguration } from '../../models/app-configuration.model';
import { PageNavigationConfiguration, isNavigationNode } from '../../models/navigation-config.model';
import {
NGX_PFE_CONFIGURATION,
NGX_PFE_CONFIG_API_FORMAT,
NgxPfeModuleConfiguration,
} from '../../models/ngx-pfe-module-configuration.model';
import { NgxPfeConfig, PageConfig } from '../../models/ngx-pfe-page-config.model';
import { PfeActionConfig } from '../../pfe-actions/pfe-actions.model';
import { PfeUtilService } from '../../pfe-util/services/util.service';
import { clone } from '../../util/clone';
export const VALIDATION_ERROR_STATE_KEY_FALLBACK = 'validationErrorData';
export const NGX_PFE_FLOW_CONFIGURATION: InjectionToken<NgxPfeConfig | Promise<NgxPfeConfig>> = new InjectionToken<
NgxPfeConfig | Promise<NgxPfeConfig>
>('NGX_PFE_FLOW_CONFIGURATION - supply the flow configuration as a token');
/**
* Service that provides the pfe configuration.
*
* We need to tell the Angular compiler that the NGX_PFE_FLOW_CONFIGURATION generic Promise token is ok:
* @dynamic
* It doesn't like the generic there, as that could cause issues during runtime (Check runs due to strictMetadataEmit).
* This will not be required anymore, once the library builds are also switched to ivy.
*
* @export
* @abstract
*/
@Injectable()
export abstract class PfeConfigurationService<
APP_CONFIG_TYPE extends AppConfiguration = AppConfiguration,
// eslint-disable-next-line @typescript-eslint/naming-convention
PAGE_CONFIG_TYPE extends PageConfig = PageConfig,
// eslint-disable-next-line @typescript-eslint/naming-convention
ACTIONS_CONFIG_TYPE = PfeActionConfig,
> {
/**
* Assign the AppConfiguration Object for quick and easy access
*/
public pfeApplicationConfiguration: AppConfiguration | undefined = undefined;
protected config$ = this.loadConfig().pipe(shareReplay(1));
protected constructor(
@Inject(NGX_PFE_CONFIGURATION) private ngxPfeModuleConfiguration: NgxPfeModuleConfiguration,
private http: HttpClient,
private pfeUtilService: PfeUtilService,
private logger: NgxLoggerService,
@Optional()
@Inject(NGX_PFE_FLOW_CONFIGURATION)
private flowConfigToken?:
| NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>
| Promise<NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>>
) {}
/**
* Get the config
*/
public getConfig(): Promise<NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>> {
return firstValueFrom(this.config$);
}
public getPageConfiguration(pageID: string | undefined): Promise<PAGE_CONFIG_TYPE | undefined> {
return this.config$
.pipe(
map((config) => config?.pagesConfiguration?.find((page) => page.pageId === pageID)),
catchError(() => of(undefined)),
first()
)
.toPromise();
}
public getPageNavigationConfiguration(pageID: string | undefined): Promise<PageNavigationConfiguration<ACTIONS_CONFIG_TYPE> | undefined> {
if (!pageID) {
return Promise.resolve(undefined);
}
return this.config$
.pipe(
map((config) => config?.navConfiguration?.pages?.find((page) => page.pageId === pageID)),
catchError(() => of(undefined)),
first()
)
.toPromise();
}
public isAppConfigurationAvailable(): Promise<boolean> {
return firstValueFrom(
this.config$.pipe(
map((config) => !!config && config?.appConfiguration !== null && config?.appConfiguration !== undefined),
catchError(() => of(false))
)
);
}
public getAppConfiguration(): Promise<APP_CONFIG_TYPE | undefined> {
return this.config$
.pipe(
map((config) => config.appConfiguration),
catchError(() => of(undefined)),
first()
)
.toPromise();
}
public getErrorMessageKey(): Promise<string> {
return firstValueFrom(
this.config$.pipe(
map((config) => config?.appConfiguration?.pfeConfig?.validationErrorStateKey ?? VALIDATION_ERROR_STATE_KEY_FALLBACK),
catchError(() => of(VALIDATION_ERROR_STATE_KEY_FALLBACK))
)
);
}
/**
* @deprecated The post handling of the config is now done in the augmentConfig() method.
*/
public addReferencedPageConfigs(
config: NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>
): NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE> {
return this.augmentConfig(config);
}
/**
* Augment the configuration for the pageConfigIdReference and navigationNode features.
* This will modify the config directly.
*/
protected augmentConfig(
config: NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>
): NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE> {
if (config?.navConfiguration?.pages && config?.pagesConfiguration) {
config.navConfiguration.pages.forEach((pageNavConfig) => {
this.handleReferencePageConfigs(config, pageNavConfig);
this.addNavigationNodeConfigs(config.pagesConfiguration, pageNavConfig);
});
}
// store the pfeConfig Object
if (config.appConfiguration) {
this.pfeApplicationConfiguration = config.appConfiguration;
}
return config;
}
/**
* Navigation page configs that are flagged as navigationNode still need to be present in the pagesConfiguration.
* This method will add them there.
*/
private addNavigationNodeConfigs(pagesConfiguration: PageConfig[], pageNavConfig: PageNavigationConfiguration<ACTIONS_CONFIG_TYPE>) {
if (isNavigationNode(pageNavConfig)) {
const pageConfigExists = pagesConfiguration.some((pageConfig) => pageConfig.pageId === pageNavConfig.pageId);
if (!pageConfigExists) {
pagesConfiguration.push({
pageId: pageNavConfig.pageId,
});
}
}
}
/**
* It is possible, to reference other pageConfigs in a navigation config.
* This method will copy the referenced configs and do a deep merge if there is
* also a navigation pageConfig.
*/
private handleReferencePageConfigs(
config: NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>,
navConfig: PageNavigationConfiguration<ACTIONS_CONFIG_TYPE>
) {
if (navConfig.pageConfigIdReference && !isNavigationNode(navConfig)) {
// If there is also a config for the pageId, a merge will be done
const navConfigPageConfig = config.pagesConfiguration.find((page) => page.pageId === navConfig.pageId);
// Get the pageConfig that is referenced here. It is copied.
const referencedPageConfig: PAGE_CONFIG_TYPE | undefined = config.pagesConfiguration.find(
(page) => page.pageId === navConfig.pageConfigIdReference
);
if (!referencedPageConfig) {
this.logger.warnToServer(`No config for pageConfigIdReference: ${navConfig.pageConfigIdReference}`);
return;
}
const referencedPageConfigCopy = clone(referencedPageConfig);
if (!referencedPageConfigCopy) {
return;
}
referencedPageConfigCopy.pageId = navConfig.pageId;
if (navConfigPageConfig) {
// Merge the navConfig pageConfig on top of the reference config and replace the navConfig pageConfig:
const index = config.pagesConfiguration.indexOf(navConfigPageConfig);
config.pagesConfiguration[index] = deepmerge(
referencedPageConfigCopy,
navConfigPageConfig,
navConfig.pageConfigIdReferenceDeepMergeArrays ? {} : { arrayMerge: this.overwriteMerge }
) as PAGE_CONFIG_TYPE;
} else {
// Add the copy of the reference pageConfig:
config.pagesConfiguration.push(referencedPageConfigCopy);
}
}
}
private loadConfig(): Observable<NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>> {
let config$: Observable<NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>>;
if (this.flowConfigToken) {
// The flowConfigToken can be a Promise, but can also be a value.
// This neat little trick turns values into a promise and if it is already a promise, it keeps the Promise
// (As in, it is not resolved immediately, but only when the original promise is resolved.
// The alternative to this would be a type guard and to handle the types differently)
const promisifiedValue = Promise.resolve(this.flowConfigToken);
config$ = from(promisifiedValue);
} else {
config$ = this.loadConfigFromBackend();
}
return config$.pipe(map((config) => this.augmentConfig(config)));
}
private loadConfigFromBackend(): Observable<NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>> {
const tenant = this.pfeUtilService.tenant;
const url =
this.ngxPfeModuleConfiguration.configApiEndpoint +
(tenant ? `/${tenant}` : '') +
`/${this.pfeUtilService.applicationId}` +
(this.ngxPfeModuleConfiguration.configApiFormat === NGX_PFE_CONFIG_API_FORMAT.STATIC ? '/index.json' : '');
return this.http.get<NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>>(url);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private overwriteMerge = (destinationArray: [], sourceArray: [], _: unknown): [] => sourceArray;
}