File

libs/ngx-pfe/services/pfe-config-service/config-service.service.ts

Description

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.

Index

Properties
Methods

Methods

Public addReferencedPageConfigs
The post handling of the config is now done in the augmentConfig() method.
addReferencedPageConfigs(config: NgxPfeConfig)
Parameters :
Name Type Optional
config NgxPfeConfig<APP_CONFIG_TYPE | PAGE_CONFIG_TYPE | ACTIONS_CONFIG_TYPE> No
Returns : NgxPfeConfig<APP_CONFIG_TYPE, PAGE_CONFIG_TYPE, ACTIONS_CONFIG_TYPE>
Public getAppConfiguration
getAppConfiguration()
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 :
Name Type Optional
pageID string | undefined No
Public getPageNavigationConfiguration
getPageNavigationConfiguration(pageID: string | undefined)
Parameters :
Name Type Optional
pageID string | undefined No
Public isAppConfigurationAvailable
isAppConfigurationAvailable()
Returns : Promise<boolean>

Properties

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

results matching ""

    No results matching ""