File

libs/ngx-pfe/pfe-actions/pfe-actions.service.ts

Index

Properties

Properties

notBusyWhileRunning
notBusyWhileRunning: boolean
Type : boolean
Optional

Use notBusyWhileRunning: true to let PFE know that the app should not be marked as busy while this action is running. This means, busy$ in the PfeBusinessService is going to be false for as long as this action is active. Currently this is only supported for onPageLeaveActions and onNavigationStartActions.

import { NgxLoggerService } from '@allianz/ngx-logger';
import { Injectable, inject } from '@angular/core';
import { ReplaySubject, Subject } from 'rxjs';
import { PfeConditionsService } from '../pfe-conditions/public-api';
import { PfeConfigurationService } from '../services/pfe-config-service/config-service.service';
import { PfeStateService } from '../services/pfe-state-service/state.service';
import { PfeNestedActions, PfeNestedActionsType } from './nested-actions/nested-actions.model';
import {
  ExecutionResult,
  GlobalActions,
  PfeActionConfig,
  PfeActionFunction,
  PfeBaseActionConfig,
  PfeGlobalAction,
} from './pfe-actions.model';
import { reloadPageAction } from './reload-page/reload-page-action';
import { ReloadPageActionType } from './reload-page/reload-page-action.model';
import { PfeResetStateActionType } from './reset-state/reset-state.model';
import { PfeResetStateService } from './reset-state/reset-state.service';
import { PfeRewindHistory } from './rewind-history/rewind-history.model';
import { PfeRewindHistoryService } from './rewind-history/rewind-history.service';
import { PfeUpdateStateOnBackend } from './update-state-on-backend/update-state-on-backend.model';
import { PfeUpdateStateOnBackendService } from './update-state-on-backend/update-state-on-backend.service';
import { PfeUpdateStateValuesType } from './update-state-values/update-state-values.model';
import { PfeUpdateStateValuesService } from './update-state-values/update-state-values.service';

interface RegisteredAction {
  func: PfeActionFunction;
  options?: RegisterActionOptions;
}

interface RegisterActionOptions {
  /**
   * Use `notBusyWhileRunning: true` to let PFE know that the app should not be marked as busy while this action is running.
   * This means, `busy$` in the PfeBusinessService is going to be `false` for as long as this action is active.
   * Currently this is only supported for `onPageLeaveActions` and `onNavigationStartActions`.
   */
  notBusyWhileRunning?: boolean;
}

@Injectable()
export class PfeActionsService {
  protected pfeStateService = inject(PfeStateService);
  protected pfeUpdateStateValuesService = inject(PfeUpdateStateValuesService);
  protected pfeUpdateStateOnBackendService = inject(PfeUpdateStateOnBackendService);
  protected pfeConfigurationService = inject(PfeConfigurationService);
  protected pfeRewindHistoryService = inject(PfeRewindHistoryService);
  protected pfeResetStateService = inject(PfeResetStateService);
  protected pfeConditionsService = inject(PfeConditionsService);
  protected logger = inject(NgxLoggerService);

  private registeredActions = new Map<string, RegisteredAction>();
  public registeredActions$ = new ReplaySubject<string[]>(1);

  private _actionExecuted$ = new Subject<PfeBaseActionConfig>();
  // eslint-disable-next-line @typescript-eslint/member-ordering
  public actionExecuted$ = this._actionExecuted$.asObservable();

  constructor() {
    this.registerDefaultActions();
  }

  /**
   * Execute an array of actions
   * @param actions The array of action
   * @param interruptBusyState Optional callback to interrupt PFE's busy state if an action is marked as `notBusyWhileRunning`
   * @returns The combined boolean response of the actions. True if all returned true. False if at least one returned false.
   */
  public async executeActions(actions: PfeBaseActionConfig[], interruptBusyState?: (value: boolean) => void): Promise<boolean> {
    if (!actions || (actions && !actions.length)) {
      return true;
    }

    const globalActions = await this.getGlobalActions();
    for (let action of actions) {
      try {
        action = await this.getGlobalAction(action, globalActions);
        const actionResult = await this.executeSpreadAction(action as PfeGlobalAction, interruptBusyState);
        if (!actionResult.actionResult || (actionResult.wasExecuted && action.break)) {
          return actionResult.actionResult;
        }
      } catch (error) {
        this.logger.errorToServer(`Error on ACTION "${action.type || action.globalConfigId}"`, (error as Error).message);
        throw error;
      }
    }
    return true;
  }

  /**
   * Get the global actions. Then spread the action with the global action (if needed)
   * Then execute the action
   * @param actionCfg Action configuration
   * @param interruptBusyState Function to call to interrupt PFE's busy state if an action is waiting for user input
   */
  public async executeAction(actionCfg: PfeBaseActionConfig, interruptBusyState?: (value: boolean) => void): Promise<boolean> {
    const action = await this.getGlobalAction(actionCfg);
    return (await this.executeSpreadAction(action, interruptBusyState)).actionResult;
  }

  /**
   * Register an action that can be invoked later
   * @param type The name of the action
   * @param action The action itself, should be an arrow async function, see the default actions
   * to see examples
   * @param options Further options for the action.
   */
  public registerAction(type: string, action: PfeActionFunction, options?: RegisterActionOptions) {
    if (this.registeredActions.has(type)) {
      throw new RangeError(`The action "${type}" its already registered`);
    }
    this.registeredActions.set(type, { func: action, options });
    this.registeredActions$.next(Array.from(this.registeredActions.keys()));
  }

  /**
   * Returns the action if there is no globalAction.
   * Else will return the action on the global action spread with the content of the action
   * @param actionCfg The action cfg
   * @param globalActionsCfg The global action config
   */
  protected async getGlobalAction(
    actionCfg: PfeBaseActionConfig,
    globalActionsCfg?: GlobalActions<PfeGlobalAction> | undefined
  ): Promise<PfeGlobalAction> {
    if (!actionCfg.globalConfigId) {
      return actionCfg as PfeGlobalAction;
    }

    if (!globalActionsCfg) {
      globalActionsCfg = await this.getGlobalActions();
    }

    if (!globalActionsCfg) {
      return actionCfg as PfeGlobalAction;
    }

    const id = actionCfg.globalConfigId;
    const globalAction = globalActionsCfg[id];
    if (!globalAction) {
      throw new RangeError(`The action with id "${id}" is not declared on the global actions`);
    }

    const { globalConfigId, ...globalActionCfg } = actionCfg;
    return {
      ...globalAction,
      ...globalActionCfg,
    } as PfeGlobalAction;
  }
  /**
   * Execute a spread action
   * @param action The action to execute
   * @returns The result that was returned from the action
   */
  private async executeSpreadAction(action: PfeGlobalAction, interruptBusyState?: (value: boolean) => void): Promise<ExecutionResult> {
    this.checkIfActionIsRegistered(action);
    const shouldExecuteAction = action.conditions
      ? this.pfeConditionsService.evaluateConditions(action.conditions, this.pfeStateService.getFullState())
      : true;
    if (shouldExecuteAction) {
      // The get can never be undefined as checkIfActionIsRegistered() already ensures that.
      // See also: https://github.com/microsoft/TypeScript/issues/9619
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      const { func, options } = this.registeredActions.get(action.type)!;
      if (options?.notBusyWhileRunning) {
        interruptBusyState?.(true);
      }
      const result = await func(action).finally(() => {
        if (options?.notBusyWhileRunning) {
          interruptBusyState?.(false);
        }
      });

      this._actionExecuted$.next(action);

      // If the action returned something not boolean, we default to true:
      return {
        wasExecuted: true,
        actionResult: typeof result === 'boolean' ? result : true,
      };
    } else {
      return {
        wasExecuted: false,
        actionResult: true,
      };
    }
  }

  /**
   * Check if an action its registered and if no it throws an error
   * @param action An action
   * @throws RangeError if the action its not registered
   */
  private checkIfActionIsRegistered(action: PfeBaseActionConfig) {
    if (!action?.type) {
      throw new Error('The given action config does not have a type');
    }
    if (!this.registeredActions.has(action.type)) {
      throw new RangeError(`The action "${action.type}" is not registered.`);
    }
  }

  /**
   * Get the global actions from the config
   */
  private async getGlobalActions(): Promise<GlobalActions<PfeGlobalAction> | undefined> {
    return (await this.pfeConfigurationService.getConfig())?.globalConfiguration?.actions;
  }

  /**
   * Register the default actions included in the Pfe
   */
  private registerDefaultActions() {
    this.registerAction(PfeUpdateStateValuesType, this.pfeUpdateStateValuesService.executeActionUpdateStatesValues);
    this.registerAction(PfeUpdateStateOnBackend, this.pfeUpdateStateOnBackendService.executeActionUpdateStateOnBackend);
    this.registerAction(PfeRewindHistory, this.pfeRewindHistoryService.rewindHistory);
    this.registerAction(ReloadPageActionType, reloadPageAction);
    this.registerAction(PfeResetStateActionType, this.pfeResetStateService.resetState);
    this.registerAction(PfeNestedActionsType, (options: PfeNestedActions<PfeActionConfig>) => this.executeActions(options.actions));
  }
}

results matching ""

    No results matching ""