File

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

Description

The PfeNavigationService is responsible to determine the next page to be displayed depending on the configuration and the current values in the state.

Index

Properties
Methods

Constructor

constructor(router: Router, pfeStateService: PfeStateService, pfeConfigService: PfeConfigurationService, pfeServiceActivatorService: PfeServiceActivatorService, pfeNavigationUtilService: PfeNavigationUtilService, pfeActionsService: PfeActionsService, logger: NgxLoggerService)
Parameters :
Name Type Optional
router Router No
pfeStateService PfeStateService No
pfeConfigService PfeConfigurationService No
pfeServiceActivatorService PfeServiceActivatorService No
pfeNavigationUtilService PfeNavigationUtilService No
pfeActionsService PfeActionsService No
logger NgxLoggerService No

Methods

Public determineUpdateStateConfig
determineUpdateStateConfig(key: string, pageNavConfig: NavOptionConfig[] | undefined)

This method determine which is the navigation config of a give key

Parameters :
Name Type Optional Description
key string No

page to be search

pageNavConfig NavOptionConfig[] | undefined No

list with all the possible configs

Public Async getErrorPage
getErrorPage(errorResponse?: HttpErrorResponse)
Parameters :
Name Type Optional
errorResponse HttpErrorResponse Yes
Public Async getFirstPage
getFirstPage(disableErrorPageNavigation?: boolean)
Parameters :
Name Type Optional
disableErrorPageNavigation boolean Yes
Public Async getFirstPageOption
getFirstPageOption(disableErrorPageNavigation?: boolean)
Parameters :
Name Type Optional
disableErrorPageNavigation boolean Yes
Public Async getFirstPageOrErrorPageOption
getFirstPageOrErrorPageOption()

Tries to get the first page. If that fails, the error page is returned. This is mostly useful during startup of the application. Instead of the first page, the error page is simply displayed.

Public Async getNextPage
getNextPage(pageNavConfig: PageNavigationConfiguration | FirstPageConfiguration)

Determine the next page to be displayed.

Parameters :
Name Type Optional Description
pageNavConfig PageNavigationConfiguration | FirstPageConfiguration No

It can be either a pageNavConfig or a first page configuration.

The next page.

Public Async getNextPageOption
getNextPageOption(pageNavConfig: PageNavigationConfiguration | FirstPageConfiguration)
Parameters :
Name Type Optional
pageNavConfig PageNavigationConfiguration | FirstPageConfiguration No
Public Async getPreviousPage
getPreviousPage(pageNavConfig: PageNavigationConfiguration)
Parameters :
Name Type Optional
pageNavConfig PageNavigationConfiguration No
Public getPreviousPageOnHistory
getPreviousPageOnHistory(omitFromHistory)
Parameters :
Name Optional Default value
omitFromHistory No false
Returns : string | undefined
Public Async getPreviousPageOption
getPreviousPageOption(pageNavConfig: PageNavigationConfiguration)
Parameters :
Name Type Optional
pageNavConfig PageNavigationConfiguration No
Public Async navigate
navigate(nextPage: string | undefined, doNotActuallyNavigate?: boolean, navigationType?: NavigationType)

Navigates directly to a specific page. Does not execute the onPageLeave service activators. Does not execute state navigation updates

but do not trigger the actual navigation. Usecase: The actual navigation is done by some other mechanism. For example the browser back button

Parameters :
Name Type Optional Description
nextPage string | undefined No

The id of the page to navigate to

doNotActuallyNavigate boolean Yes

Do everything that is related to a navigation, but do not trigger the actual navigation. Usecase: The actual navigation is done by some other mechanism. For example the browser back button

navigationType NavigationType Yes
Public navigateBack
navigateBack(doNotActuallyNavigate?: boolean)

Navigate to the previous page in the flow. PfeNavigationService.navigate

Parameters :
Name Type Optional
doNotActuallyNavigate boolean Yes
Public navigateNext
navigateNext(doNotActuallyNavigate?: boolean)

Navigate to the next page. Also triggers all actions/service activators, etc... PfeNavigationService.navigate

Parameters :
Name Type Optional
doNotActuallyNavigate boolean Yes
Public Async navigateNextWithConfig
navigateNextWithConfig(currentPageNavCfg: PageNavigationConfiguration | undefined, doNotActuallyNavigate?: boolean)
Parameters :
Name Type Optional
currentPageNavCfg PageNavigationConfiguration | undefined No
doNotActuallyNavigate boolean Yes
Public Async navigateToErrorPage
navigateToErrorPage(errorResponse?: HttpErrorResponse)
Parameters :
Name Type Optional
errorResponse HttpErrorResponse Yes
Returns : unknown
Public Async navigateToPageId
navigateToPageId(pageId: string, triggerLeaveActions?: boolean, doNotActuallyNavigate?: boolean)

Navigate to a given pageId

Parameters :
Name Type Optional Description
pageId string No

The page id

triggerLeaveActions boolean Yes

If should trigger the page leave actions

doNotActuallyNavigate boolean Yes
Public popPageFromHistory
popPageFromHistory(omitFromHistory: boolean | undefined)
Parameters :
Name Type Optional
omitFromHistory boolean | undefined No
Returns : any
Public pushPageToHistory
pushPageToHistory(pageID: string)
Parameters :
Name Type Optional
pageID string No
Returns : void
Public Async updateStateOnBackend
Please switch to updateStoredState() and handle the storage of the last visited page on the calling side.
updateStateOnBackend(currentPageName: string, navConfig: PageNavigationConfiguration)
Parameters :
Name Type Optional
currentPageName string No
navConfig PageNavigationConfiguration No
Returns : any
Public Async updateStoredState
updateStoredState(navConfig: PageNavigationConfiguration)

Updates the state on a remote backend or the session storage. Checks if the automatic state storage is still active and doesn't do anything if it is shutdown.

Parameters :
Name Type Optional
navConfig PageNavigationConfiguration No
Returns : any

Properties

Public currentPageId$
Type : BehaviorSubject<string | undefined>
Default value : this.pfeNavigationUtilService.currentPageId$
Public navigationInProgress$
Type : BehaviorSubject<boolean>
Default value : new BehaviorSubject<boolean>(false)

Set to true, when a navigation or network call (service activator call) is currently running.

Public pageStatus$
Default value : new BehaviorSubject<boolean>(false)

Determines if the current page is considered valid. If it is, navigation is allowed. This not only affects the next/back button but also the browser navigation.

import { NgxLoggerService } from '@allianz/ngx-logger';
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ChildActivationEnd, Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
import { FirstPageConfiguration, NavOptionConfig, NavigationType, PageNavigationConfiguration } from '../../models/navigation-config.model';
import { NgxPfeConfig, PageConfig } from '../../models/ngx-pfe-page-config.model';
import { PfeUserInputState } from '../../models/pfe-state/user-input-state.model';
import { NavServiceActivatorConfigInternally } from '../../pfe-service-activator/service/service-activator.model';
import { PfeServiceActivatorService } from '../../pfe-service-activator/service/service-activator.service';
import { clone } from '../../util/clone';
import { PfeConfigurationService } from '../pfe-config-service/config-service.service';
import { BYPASS_PFE_ROUTING } from '../pfe-routing/route-generator-guards.definition';
import { getPageIDFromRouteSnapshot } from '../pfe-routing/utils/page-id-from-route-snapshot';
import { PFE_HISTORY_KEY, PFE_VISITED_KEY } from '../pfe-state-service/pfe-state-keys';
import { PfeStateService } from '../pfe-state-service/state.service';
import { PfeActionsService } from './../../pfe-actions/pfe-actions.service';
import { LAST_VISITED_PAGE_STATE_KEY, PfeNavigationUtilService } from './navigation-util.service';

/**
 * The PfeNavigationService is responsible to determine the next page to be displayed
 * depending on the configuration and the current values in the state.
 *
 * @export
 */
@Injectable()
export class PfeNavigationService {
  private readonly PREVENT_REPEATED_NAVIGATION_DURATION = 300;

  /**
   * Set true, while throttling navigation events
   */
  private throttledActive = false;

  constructor(
    private router: Router,
    private pfeStateService: PfeStateService,
    private pfeConfigService: PfeConfigurationService,
    private pfeServiceActivatorService: PfeServiceActivatorService,
    private pfeNavigationUtilService: PfeNavigationUtilService,
    private pfeActionsService: PfeActionsService,
    private logger: NgxLoggerService
  ) {
    // Even though router events do not differentiate between primary and secondary outlets
    // it is possible to subscribe to the ChildActivationEnd events.
    // These contain the ActivatedRouteSnapshot, which allows to filter out all non-pfe routes.
    this.router.events
      .pipe(
        filter((event) => event instanceof ChildActivationEnd),
        map((event) => {
          const childEvent = event as ChildActivationEnd;
          return getPageIDFromRouteSnapshot(childEvent.snapshot);
        }),
        filter((pageID) => pageID != null),
        distinctUntilChanged()
      )
      .subscribe((pageID) => {
        (async () => {
          const pageNavConfig: PageNavigationConfiguration | undefined = await this.pfeConfigService.getPageNavigationConfiguration(pageID);
          if (pageNavConfig && !this.getSkipLocationChangeSetting(pageNavConfig) && pageID) {
            if (!history?.state?.[BYPASS_PFE_ROUTING]) {
              this.pushPageToHistory(pageID);
            }
          }
        })();
        this.pfeNavigationUtilService.currentPageId$.next(pageID);
      });
  }

  /**
   * Set to true, when a navigation or network call (service activator call) is currently running.
   */
  public navigationInProgress$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

  public currentPageId$: BehaviorSubject<string | undefined> = this.pfeNavigationUtilService.currentPageId$;

  /**
   * Determines if the current page is considered valid.
   * If it is, navigation is allowed. This not only affects the next/back button
   * but also the browser navigation.
   */
  public pageStatus$ = new BehaviorSubject<boolean>(false);

  /**
   * Will be set to true, when the state handling has been shutdown within a flow.
   */
  // TODO: Move to state service:
  private stateStorageIsShutdown = false;

  /**
   * Navigate to the next page.
   * Also triggers all actions/service activators, etc...
   * {@link PfeNavigationService.navigate}
   */
  public navigateNext(doNotActuallyNavigate?: boolean): Promise<PageConfig | undefined> {
    if (this.throttledActive) {
      return Promise.resolve(undefined);
    }

    this.throttledActive = true;
    setTimeout(() => {
      this.throttledActive = false;
    }, this.PREVENT_REPEATED_NAVIGATION_DURATION);

    return this._navigateNext(doNotActuallyNavigate);
  }

  private async _navigateNext(doNotActuallyNavigate?: boolean): Promise<PageConfig | undefined> {
    const currentPageNavCfg: PageNavigationConfiguration | undefined = await this.pfeConfigService.getPageNavigationConfiguration(
      this.currentPageId$.value
    );

    return this.navigateNextWithConfig(currentPageNavCfg, doNotActuallyNavigate);
  }

  public async navigateNextWithConfig(
    currentPageNavCfg: PageNavigationConfiguration | undefined,
    doNotActuallyNavigate?: boolean
  ): Promise<PageConfig | undefined> {
    if (!currentPageNavCfg?.nextOptionList) {
      return;
    }

    this.navigationInProgress$.next(true);

    try {
      const actionsResult = await this.navigateNextActions(currentPageNavCfg);
      if (!actionsResult) {
        this.navigationInProgress$.next(false);
        return;
      }
    } catch (e) {
      this.navigationInProgress$.next(false);
      // Error handling is done in httpErrorHandler
      return;
    }

    try {
      let nextPageId: string | undefined;

      const nextPageOption: NavOptionConfig | undefined = await this.evaluateNavOptions(currentPageNavCfg.nextOptionList);
      if (this.pfeNavigationUtilService.navigateExternal(nextPageOption)) {
        return;
      }
      if (nextPageOption?.nextPageId) {
        nextPageId = nextPageOption?.nextPageId;

        const navToUpdate: NavOptionConfig | undefined = this.determineUpdateStateConfig(nextPageId, currentPageNavCfg.nextOptionList);
        /** @todo DEPRECATED, will be remove after the implementation of PFE Actions */
        if (navToUpdate?.updateStateValues) {
          this.pfeStateService.updateStateValues(navToUpdate.updateStateValues);
        }
      }

      if (nextPageId) {
        let pageIdsToRemove = [];
        pageIdsToRemove = currentPageNavCfg.nextOptionList
          .filter(
            (page) =>
              page.nextPageId &&
              page.nextPageId !== nextPageId &&
              Object.prototype.hasOwnProperty.call(this.pfeStateService._staleState.pages, page.nextPageId)
          )
          .map((page) => page.nextPageId);

        this.pfeStateService.analyzePageForStale({
          pageId: nextPageId,
          removePageFromState: pageIdsToRemove as string[],
        });

        return await this.navigate(nextPageId, doNotActuallyNavigate, NavigationType.FORWARD);
      } else {
        this.navigationInProgress$.next(false);
        this.logger.warnToServer('Next navigation options config did not lead to a result: ', currentPageNavCfg);
      }
    } catch (e) {
      this.navigationInProgress$.next(false);
      throw e;
    }
  }

  /**
   * Execute all the actions related with the "onPageLeave*":
   * - Execute the onPageLeaveActions
   * - Execute the onPageLeaveServiceActivators
   * - Check for a new error key.
   * @throws Error if an error happens on the service activators or the actions.
   * Also rejects if the "errorMessage" is present
   * @param currentPageNavCfg The page configuration.
   * @returns A promise that signals if all actions returned a positive status.
   */
  private async navigateNextActions(currentPageNavCfg: PageNavigationConfiguration | undefined): Promise<boolean> {
    if (!currentPageNavCfg) {
      return true;
    }
    // clean error message data on navigation so message is not still displayed
    this.pfeStateService.storeValue(await this.pfeConfigService.getErrorMessageKey(), undefined);

    if (currentPageNavCfg.onPageLeaveActions) {
      const actionsResult = await this.pfeActionsService.executeActions(currentPageNavCfg.onPageLeaveActions, (notBusy) =>
        this.navigationInProgress$.next(!notBusy)
      );
      if (!actionsResult) {
        return false;
      }
    }

    /** @todo DEPRECATED, will be remove after the implementation of PFE Actions */
    if (currentPageNavCfg.onPageLeaveServiceActivators) {
      await this.pfeServiceActivatorService.spreadWithGlobalServiceActivatorConfig(currentPageNavCfg.onPageLeaveServiceActivators);
      await this.pfeServiceActivatorService.handleServiceActivators(
        currentPageNavCfg.onPageLeaveServiceActivators,
        currentPageNavCfg.executeServiceActivatorsSync
      );
    }

    const errorMessageKey = await this.pfeConfigService.getErrorMessageKey();
    const errorMessage = this.pfeStateService.getValue(errorMessageKey);
    if (errorMessage) {
      return Promise.reject(undefined);
    }
    return true;
  }

  /**
   * Navigate to the previous page in the flow.
   * {@link PfeNavigationService.navigate}
   */
  public navigateBack(doNotActuallyNavigate?: boolean): Promise<PageConfig | undefined> {
    if (this.throttledActive) {
      return Promise.resolve(undefined);
    }

    this.throttledActive = true;
    setTimeout(() => {
      this.throttledActive = false;
    }, this.PREVENT_REPEATED_NAVIGATION_DURATION);

    return this._navigateBack(doNotActuallyNavigate);
  }

  private async _navigateBack(doNotActuallyNavigate?: boolean): Promise<PageConfig | undefined> {
    const currentPage = this.currentPageId$.value;
    const currentPageNavCfg: PageNavigationConfiguration | undefined =
      await this.pfeConfigService.getPageNavigationConfiguration(currentPage);

    let nextPageId!: string;

    if (currentPageNavCfg) {
      this.navigationInProgress$.next(true);
      try {
        const actionsResult = await this.navigateBackActions(currentPageNavCfg);
        if (!actionsResult) {
          this.navigationInProgress$.next(false);
          return undefined;
        }
      } catch (error) {
        this.navigationInProgress$.next(false);
        // Error handling is done in httpErrorHandler for service activators and in the actions for actions.
        return undefined;
      }

      if (currentPageNavCfg.backOptionList) {
        const backPageOption: NavOptionConfig | undefined = await this.evaluateNavOptions(currentPageNavCfg.backOptionList);
        if (this.pfeNavigationUtilService.navigateExternal(backPageOption)) {
          return undefined;
        }
        if (backPageOption?.nextPageId) {
          nextPageId = backPageOption?.nextPageId;
        }

        const navToUpdate: NavOptionConfig | undefined = this.determineUpdateStateConfig(nextPageId, currentPageNavCfg.backOptionList);
        if (navToUpdate?.updateStateValues) {
          this.pfeStateService.updateStateValues(navToUpdate.updateStateValues);
        }
      }
    }

    // There was no backOptionList or no result. What we're gonna do right here is go back, way back, back into time
    // Also: If there is an empty backOptionList configured, no backwards navigation is done and the fallback is
    // determined with the backOptionListFallBackToHistory flag
    if (!nextPageId) {
      if (
        !currentPageNavCfg?.backOptionList ||
        (currentPageNavCfg?.backOptionList?.length !== 0 &&
          (await this.pfeConfigService.getAppConfiguration())?.pfeConfig?.backOptionListFallBackToHistory)
      ) {
        const pageIdFromHistory = this.getPreviousPageOnHistory(currentPageNavCfg?.omitFromHistory);
        if (pageIdFromHistory) {
          nextPageId = pageIdFromHistory;
        }
      }
    }

    if (nextPageId) {
      // start staleState analysis for back button trigger
      if (currentPage) {
        this.pfeStateService.analyzePageForStale({
          pageId: nextPageId,
          removePageFromState: [currentPage],
        });
      }

      return await this.navigate(nextPageId, doNotActuallyNavigate, NavigationType.BACKWARD);
    } else {
      this.navigationInProgress$.next(false);
      this.logger.warnToServer('Back navigation options config did not lead to a result: ', currentPageNavCfg);
    }
  }

  /**
   * Execute all the actions related with the "onPageLeaveBackwardsActions":
   * - Execute the onPageLeaveBackwardsActions
   * @throws Error if an error happens on the actions.
   * @param currentPageNavCfg The page configuration.
   * @returns A promise that signals if all actions returned a positive status.
   */
  private async navigateBackActions(currentPageNavCfg: PageNavigationConfiguration): Promise<boolean> {
    // clean error message data on navigation so message is not still displayed
    this.pfeStateService.storeValue(await this.pfeConfigService.getErrorMessageKey(), undefined);

    if (currentPageNavCfg.onPageLeaveBackwardsActions) {
      const actionsResult = await this.pfeActionsService.executeActions(currentPageNavCfg.onPageLeaveBackwardsActions);
      return actionsResult;
    }
    return true;
  }

  /**
   * Navigates directly to a specific page.
   * Does not execute the onPageLeave service activators.
   * Does not execute state navigation updates
   *
   * @param nextPage The id of the page to navigate to
   * @param doNotActuallyNavigate Do everything that is related to a navigation,
   * but do not trigger the actual navigation. Usecase: The actual navigation is done by some other
   * mechanism. For example the browser back button
   */
  public async navigate(
    nextPage: string | undefined,
    doNotActuallyNavigate?: boolean,
    navigationType?: NavigationType
  ): Promise<PageConfig | undefined> {
    if (!nextPage) {
      this.logger.errorToServer('Navigation was triggered without a pageID. Navigating nowhere...');
      return;
    }

    if (nextPage !== this.currentPageId$.value) {
      const nextPageConfig = await this.pfeConfigService.getPageConfiguration(nextPage);
      const nextPageConfigNavConfig: PageNavigationConfiguration | undefined =
        await this.pfeConfigService.getPageNavigationConfiguration(nextPage);

      if (nextPageConfig) {
        if (!doNotActuallyNavigate) {
          await this.pfeNavigationUtilService.navigateRelative(nextPage, {
            skipLocationChange: this.getSkipLocationChangeSetting(nextPageConfigNavConfig),
            state: { navigationType },
          });
        }
      } else {
        this.logger.errorToServer(`No configuration for page: ${nextPage}`);
        await this.navigateToErrorPage();
      }

      return nextPageConfig;
    }
  }

  /**
   * Navigate to a given pageId
   * @param pageId The page id
   * @param triggerLeaveActions If should trigger the page leave actions
   */
  public async navigateToPageId(
    pageId: string,
    triggerLeaveActions?: boolean,
    doNotActuallyNavigate?: boolean
  ): Promise<PageConfig | undefined> {
    if (triggerLeaveActions) {
      const currentPageNavCfg: PageNavigationConfiguration | undefined = await this.pfeConfigService.getPageNavigationConfiguration(
        this.currentPageId$.value
      );
      const actionsResult = await this.navigateNextActions(currentPageNavCfg);
      if (!actionsResult) {
        return undefined;
      }
    }
    return await this.navigate(pageId, doNotActuallyNavigate, NavigationType.FORWARD);
  }

  private getSkipLocationChangeSetting(pageNavConfig: PageNavigationConfiguration | undefined): boolean {
    let skipLocationChangeSetting = false;
    if (pageNavConfig && pageNavConfig.omitFromHistory === true) {
      skipLocationChangeSetting = true;
    }
    return skipLocationChangeSetting;
  }

  /**
   * Updates the state on a remote backend or the session storage.
   * Checks if the automatic state storage is still active and doesn't do anything if it is shutdown.
   */
  public async updateStoredState(navConfig: PageNavigationConfiguration) {
    if (this.stateStorageIsShutdown || (navConfig && navConfig.shutdownStateStorage)) {
      this.stateStorageIsShutdown = true;
      return;
    }
    await this.pfeStateService.storeState();
  }

  /**
   * @deprecated Please switch to updateStoredState() and handle the storage of the last visited page on the calling side.
   */
  public async updateStateOnBackend(currentPageName: string, navConfig: PageNavigationConfiguration) {
    if (!navConfig || (navConfig && !navConfig.omitFromHistory)) {
      this.pfeStateService.storeValue(LAST_VISITED_PAGE_STATE_KEY, currentPageName);
    }
    this.updateStoredState(navConfig);
  }

  public pushPageToHistory(pageID: string) {
    let history = this.pfeStateService.getValue(PFE_HISTORY_KEY) as string[],
      visitedPages = this.pfeStateService.getValue(PFE_VISITED_KEY) as string[];

    if (!history) {
      history = new Array<string>();
    }

    if (!visitedPages) {
      visitedPages = new Array<string>();
    }

    // TODO: Check if the current top page is the same, only add if not.

    // the page is not added if it is already there
    if (visitedPages.indexOf(pageID) === -1) {
      const lastVisited = visitedPages[visitedPages.length - 1],
        historyLast = history[history.length - 1];

      // if it the previous page is the same adding it at the end
      // if not adding it to after history last one index
      if (lastVisited === historyLast) {
        visitedPages.push(pageID);
      } else {
        const lastPageIndex = visitedPages.indexOf(historyLast);
        visitedPages.splice(lastPageIndex + 1, 0, pageID);
      }
    }

    history.push(pageID);

    this.pfeStateService.storeValue(PFE_HISTORY_KEY, history);
    this.pfeStateService.storeValue(PFE_VISITED_KEY, visitedPages);
  }

  public popPageFromHistory(omitFromHistory: boolean | undefined) {
    const history: string[] = this.pfeStateService.getValue(PFE_HISTORY_KEY) as string[];

    if (history && history.length >= 1) {
      // If the current page was not added to the history, we don't need to skip it when we're going back:
      const goBackNumber = omitFromHistory === true ? 1 : 2;

      const page = history.splice(history.length - goBackNumber, goBackNumber)[0];

      this.pfeStateService.storeValue(PFE_HISTORY_KEY, history);
      return page;
    }
  }

  public getPreviousPageOnHistory(omitFromHistory = false): string | undefined {
    const history: string[] = this.pfeStateService.getValue(PFE_HISTORY_KEY) as string[];

    if (!history || history.length === 0) {
      return;
    }

    // if the current page is the only one in the history, then there is no known previous page
    const currentPageIsOnlyHistoryEntry = history.length === 1 && !omitFromHistory;
    if (currentPageIsOnlyHistoryEntry) {
      return;
    }

    // If the current page was not added to the history, we don't need to skip it when we're going back:
    const goBackNumber = omitFromHistory ? 1 : 2;
    return history[history.length - goBackNumber];
  }

  /**
   * Determine the next page to be displayed.
   *
   * @param pageNavConfig It can be either a pageNavConfig or a first page configuration.
   * @returns The next page.
   * @throws Exception, if a sync service activator call fails.
   */
  public async getNextPage(pageNavConfig: PageNavigationConfiguration | FirstPageConfiguration): Promise<string | undefined> {
    return (await this.evaluateNavOptions(pageNavConfig.nextOptionList))?.nextPageId;
  }

  public async getNextPageOption(
    pageNavConfig: PageNavigationConfiguration | FirstPageConfiguration
  ): Promise<NavOptionConfig | undefined> {
    return this.evaluateNavOptions(pageNavConfig.nextOptionList);
  }

  public async getPreviousPage(pageNavConfig: PageNavigationConfiguration): Promise<string | undefined> {
    return (await this.evaluateNavOptions(pageNavConfig.backOptionList))?.nextPageId;
  }

  public async getPreviousPageOption(pageNavConfig: PageNavigationConfiguration): Promise<NavOptionConfig | undefined> {
    return this.evaluateNavOptions(pageNavConfig.backOptionList);
  }

  /**
   * Return the following page to be displayed. This is independent of next or back.
   *
   * @param pageNavConfig The page navigation configuration.
   * @returns The ID of the next page to be displayed. Undefined, if no next page could be determined.
   */
  private async evaluateNavOptions(pageNavConfig: NavOptionConfig[] | undefined): Promise<NavOptionConfig | undefined> {
    if (pageNavConfig) {
      const state: PfeUserInputState = this.pfeStateService.getFullState();
      const navOptionConfig = this.pfeNavigationUtilService.determineNextPage(pageNavConfig, state);

      if (navOptionConfig) {
        if (navOptionConfig?.metaNavigation?.metaNavId) {
          return { ...navOptionConfig, nextPageId: await this.getFirstPage() };
        } else {
          return navOptionConfig;
        }
      }
    }
  }

  // Forward some calls to the util service. This is done to keep the public API consistent.
  public async navigateToErrorPage(errorResponse?: HttpErrorResponse) {
    return this.pfeNavigationUtilService.navigateToGlobalErrorPage(errorResponse);
  }

  /**
   * Tries to get the first page. If that fails, the error page is returned.
   * This is mostly useful during startup of the application. Instead of the first page,
   * the error page is simply displayed.
   */
  public async getFirstPageOrErrorPageOption(): Promise<NavOptionConfig | undefined> {
    let firstPage!: NavOptionConfig | undefined;
    let error;
    try {
      firstPage = await this.getFirstPageOption(true);
    } catch (e) {
      error = e;
    }

    if (!firstPage) {
      return this.isHttpErrorResponse(error)
        ? await this.pfeNavigationUtilService.getGlobalErrorPageOption(error)
        : await this.pfeNavigationUtilService.getGlobalErrorPageOption();
    }

    return firstPage;
  }

  public async getFirstPage(disableErrorPageNavigation?: boolean): Promise<string | undefined> {
    return (await this.getFirstPageOption(disableErrorPageNavigation))?.nextPageId;
  }

  public async getFirstPageOption(disableErrorPageNavigation?: boolean): Promise<NavOptionConfig | undefined> {
    const navConfiguration = ((await this.pfeConfigService.getConfig()) as NgxPfeConfig).navConfiguration;
    const firstPageConfig: string | FirstPageConfiguration | undefined = navConfiguration.firstPage || undefined;
    let firstPage: NavOptionConfig | undefined;

    if (firstPageConfig) {
      if (this.isFirstPageConfig(firstPageConfig)) {
        if (firstPageConfig.onEnterActions) {
          try {
            const actionsResult = await this.pfeActionsService.executeActions(firstPageConfig.onEnterActions);
            if (!actionsResult) {
              return undefined;
            }
          } catch (error) {
            // Error handling of the service activators is done in showErrorPage
            if (disableErrorPageNavigation) {
              throw error;
            }
          }
        }
        /** @todo DEPRECATED, will be remove after the implementation of PFE Actions */
        if (firstPageConfig.onEnterServiceActivators) {
          try {
            await this.pfeServiceActivatorService.spreadWithGlobalServiceActivatorConfig(firstPageConfig.onEnterServiceActivators);

            let firstPageServiceActivators: NavServiceActivatorConfigInternally[];
            if (disableErrorPageNavigation) {
              // 1. Clone the config, we don't want to pollute it...
              firstPageServiceActivators = clone(firstPageConfig.onEnterServiceActivators);
              // 2. Set the _firstPageServiceActivator flag to disable the standard error handling
              // Error navigation has to be done by the caller
              firstPageServiceActivators.forEach((serviceActivator) => {
                serviceActivator._disableErrorPageNavigation = true;
              });
            } else {
              firstPageServiceActivators = firstPageConfig.onEnterServiceActivators;
            }

            await this.pfeServiceActivatorService.handleServiceActivators(firstPageServiceActivators);
          } catch (error) {
            // Error handling of the service activators is done in showErrorPage
            if (disableErrorPageNavigation) {
              throw error;
            }
          }
        }

        firstPage = await this.getNextPageOption(firstPageConfig);
      } else {
        firstPage = { nextPageId: firstPageConfig };
      }
    }

    if (!firstPage) {
      firstPage = await this.pfeNavigationUtilService.getFirstPageOption();
    }

    // register the firstPage
    if (firstPage?.nextPageId) {
      this.pfeStateService.analyzePageForStale({
        pageId: firstPage?.nextPageId,
      });
    }

    return firstPage;
  }

  public async getErrorPage(errorResponse?: HttpErrorResponse): Promise<string | undefined> {
    return this.pfeNavigationUtilService.getErrorPage(errorResponse);
  }

  private isFirstPageConfig(pageConfig: string | FirstPageConfiguration): pageConfig is FirstPageConfiguration {
    if (pageConfig && (pageConfig as PageNavigationConfiguration).nextOptionList !== undefined) {
      return true;
    } else {
      return false;
    }
  }

  private isHttpErrorResponse(error: HttpErrorResponse | unknown): error is HttpErrorResponse {
    return error instanceof HttpErrorResponse;
  }

  /**
   * This method determine which is the navigation config of a give key
   * @param key page to be search
   * @param pageNavConfig list with all the possible configs
   */
  public determineUpdateStateConfig(key: string, pageNavConfig: NavOptionConfig[] | undefined): NavOptionConfig | undefined {
    if (!pageNavConfig || pageNavConfig.length === 0) {
      return undefined;
    }
    return pageNavConfig.find((nav) => nav.nextPageId === key);
  }
}

results matching ""

    No results matching ""