libs/pfe-connector/src/lib/frame/pfe-frame-navigation.service.ts
Properties |
Methods |
constructor(pfeBusinessService: PfeBusinessService, pfeConditionsService: PfeConditionsService, pfeConfigurationService: PfeConfigurationService, pfeStateService: PfeStateService, pfeServiceActivatorService: PfeServiceActivatorService, pfeNavigationService: PfeNavigationService, talyPageService: TalyPageService, talyPageDataService: TalyPageDataService, navigationConfig: NavigationConfig, backLinkUtilsService: BackLinkUtilsService, actionsService: PfeActionsService, pfeNavigationUtilService: PfeNavigationUtilService)
|
|||||||||||||||||||||||||||||||||||||||
Parameters :
|
cancel |
cancel()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:207
|
Returns :
void
|
getOfferCode$ | ||||||
getOfferCode$(stateKey: string)
|
||||||
Inherited from
TalyFrameNavigationService
|
||||||
Defined in
TalyFrameNavigationService:213
|
||||||
Parameters :
Returns :
Observable<string | undefined>
|
gotoErrorPage | ||||||
gotoErrorPage(errorResponse?: HttpErrorResponse)
|
||||||
Inherited from
TalyFrameNavigationService
|
||||||
Defined in
TalyFrameNavigationService:197
|
||||||
Method to navigate to the error page
Parameters :
Returns :
void
|
gotoPage | ||||||
gotoPage(pageId: string)
|
||||||
Inherited from
TalyFrameNavigationService
|
||||||
Defined in
TalyFrameNavigationService:167
|
||||||
Parameters :
Returns :
void
|
gotoSection | ||||||
gotoSection(sectionId: string)
|
||||||
Inherited from
TalyFrameNavigationService
|
||||||
Defined in
TalyFrameNavigationService:178
|
||||||
Method to navigate to a particular section with given section id page of the section. It might change based on the conditions in pfe navigation configuration as well.
Parameters :
Returns :
void
|
init |
init()
|
Returns :
void
|
initApplicationBusy$ |
initApplicationBusy$()
|
Returns :
void
|
initApplicationBusyMessage$ | ||||||
initApplicationBusyMessage$(busy$: Observable
|
||||||
Parameters :
Returns :
void
|
navigateBack |
navigateBack()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:154
|
Returns :
void
|
Async navigateHome |
navigateHome()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:158
|
Returns :
any
|
Async navigateNext |
navigateNext()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:105
|
Returns :
any
|
saveOffer |
saveOffer()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:201
|
Returns :
void
|
Public disableTitleUpdates |
disableTitleUpdates()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:78
|
Returns :
void
|
Public setSections | ||||||||
setSections(sections: SectionConfig)
|
||||||||
Inherited from
TalyFrameNavigationService
|
||||||||
Defined in
TalyFrameNavigationService:67
|
||||||||
Parameters :
Returns :
void
|
Public setTranslatedTitle | ||||||
setTranslatedTitle(translatedTitle: string)
|
||||||
Inherited from
TalyFrameNavigationService
|
||||||
Defined in
TalyFrameNavigationService:82
|
||||||
Parameters :
Returns :
void
|
Readonly APPLICATION_BUSY_DELAY |
Type : number
|
Default value : 1000
|
applicationBusy$ |
Type : Observable<boolean>
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:53
|
applicationBusyMessage$ |
Type : Observable<ExtendedDisplayMessage | undefined>
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:54
|
Readonly MINIMUM_MESSAGE_TIME |
Type : number
|
Default value : 1000
|
Public navigationConfig |
Type : NavigationConfig
|
Decorators :
@Inject(TALY_FRAME_NAVIGATION_CONFIG)
|
pageActionStatus$ |
Type : Observable<PageActionStatus>
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:52
|
currentSectionStates$ |
Default value : this._currentSectionStates$.asObservable()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:26
|
history$ |
Default value : this._history$.asObservable()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:29
|
visibleSections$ |
Default value : this._visibleSections$.asObservable()
|
Inherited from
TalyFrameNavigationService
|
Defined in
TalyFrameNavigationService:33
|
import {
ExpressionCondition,
ExtendedDisplayMessage,
JSON_PATH_REGEX,
PfeActionsService,
PfeBusinessService,
PfeConditionsService,
PfeConfigurationService,
PfeNavigationService,
PfeNavigationUtilService,
PfeServiceActivatorService,
PfeStateService,
ServiceActivatorProgressDetails
} from '@allianz/ngx-pfe';
import {
NavigationConfig,
PageActionStatus,
PageHistory,
SectionConfig,
TALY_FRAME_NAVIGATION_CONFIG,
TalyFrameNavigationService
} from '@allianz/taly-common/frame';
import { BackLinkUtilsService } from '@allianz/taly-common/web-components';
import { TalyPageDataService, TalyPageService, TalyStickyService } from '@allianz/taly-core';
import { HttpErrorResponse } from '@angular/common/http';
import {
ApplicationRef,
Inject,
inject,
Injectable,
Renderer2,
RendererFactory2
} from '@angular/core';
import { combineLatest, EMPTY, merge, Observable, of, timer } from 'rxjs';
import {
debounce,
debounceTime,
distinctUntilChanged,
filter,
map,
switchMap,
take,
tap,
timeInterval
} from 'rxjs/operators';
import { createPageActionStatusObservable } from './page-action-status';
export const PFE_HISTORY_KEY = 'pfeHistory';
@Injectable()
export class PfeFrameNavigationService extends TalyFrameNavigationService {
pageActionStatus$!: Observable<PageActionStatus>;
applicationBusy$!: Observable<boolean>;
applicationBusyMessage$!: Observable<ExtendedDisplayMessage | undefined>;
readonly APPLICATION_BUSY_DELAY = 1000;
readonly MINIMUM_MESSAGE_TIME = 1000;
private applicationRef = inject(ApplicationRef);
private talyStickyService = inject(TalyStickyService);
private renderer!: Renderer2;
private rendererFactory = inject(RendererFactory2);
constructor(
private pfeBusinessService: PfeBusinessService,
private pfeConditionsService: PfeConditionsService,
private pfeConfigurationService: PfeConfigurationService,
private pfeStateService: PfeStateService,
private pfeServiceActivatorService: PfeServiceActivatorService,
private pfeNavigationService: PfeNavigationService,
private talyPageService: TalyPageService,
private talyPageDataService: TalyPageDataService,
@Inject(TALY_FRAME_NAVIGATION_CONFIG) public navigationConfig: NavigationConfig,
private backLinkUtilsService: BackLinkUtilsService,
private actionsService: PfeActionsService,
private pfeNavigationUtilService: PfeNavigationUtilService
) {
super();
this.init();
this.renderer = this.rendererFactory.createRenderer(null, null);
}
init() {
createPfeHistoryObservable(this.pfeStateService)
.pipe(
tap((value) => {
this.updateHistory(value.history);
})
)
.subscribe();
this.pageActionStatus$ = createPageActionStatusObservable(
this.pfeServiceActivatorService,
this.pfeNavigationService,
this.pfeConfigurationService,
this.pfeBusinessService,
this.backLinkUtilsService,
this.talyPageDataService,
this.navigationConfig.nextButtonDisabledWhenPageInvalid ?? false
);
this.initApplicationBusy$();
}
async navigateNext() {
function hasErrorNotification() {
return document.querySelector('.error-notification-wrapper');
}
const scrollToTopOfFrame = () => {
const frameContent = document.querySelector('#frameContent');
if (frameContent) {
const scrollMargin = this.talyStickyService.getStickyElementHeights();
this.renderer.setStyle(frameContent, 'scroll-margin-top', `${scrollMargin}px`);
frameContent.scrollIntoView({ behavior: 'smooth' });
}
};
// WHEN PAGE IS NOT COMPLETE
if (!this.pfeBusinessService.pageStatus$.value) {
if (this.navigationConfig.nextButtonDisabledWhenPageInvalid) {
return;
}
if (hasErrorNotification()) {
scrollToTopOfFrame();
return;
}
this.talyPageService.onNextPageRequested();
return;
}
// WHEN PAGE IS COMPLETE
// forward the option doNotActuallyNavigate = true to prevent PFE from navigating
// the actual navigation will be done once TALY makes sure that there is no error notification on a page
// the pfeNavigationService will take care of executing the "onPageLeave" service activator and action.
const pageConfig = await this.pfeNavigationService.navigateNext(true);
// trigger the notification component to re-render after the actions are executed
this.applicationRef.tick();
if (hasErrorNotification()) {
scrollToTopOfFrame();
// Normally, PFE takes care of sending the `navigationInProgress$.next(false)` when a navigation is success or cancelled
// But now that we handle the navigation logic within TALY, this has to be taken care of manually
// Otherwise, the spinner will keep running and block the entire page.
this.pfeNavigationService.navigationInProgress$.next(false);
return;
}
this.pfeNavigationUtilService.navigateRelative(pageConfig?.pageId as string);
}
navigateBack() {
this.pfeBusinessService.navigateBack();
}
async navigateHome() {
const firstPageId = await this.pfeBusinessService.getFirstPage();
if (!firstPageId) {
throw new Error('Trying to navigate to the Homepage: PFE returned an undefined first page.');
}
this.gotoPage(firstPageId);
}
gotoPage(pageId: string): void {
this.pfeBusinessService.navigateToPage(pageId);
}
/**
* Method to navigate to a particular section with given section id
*
* @todo Implement this function. It might not be always the first
* page of the section. It might change based on the conditions in
* pfe navigation configuration as well.
*/
gotoSection(sectionId: string): void {
const sections = this._visibleSections$.value;
const section = sections.get(sectionId);
const pages = section?.pages || [];
if (pages?.length === 0) {
throw new Error(
`Could not find proper configuration to navigate to the section ${sectionId}`
);
}
// Navigating to first page of the section
this.gotoPage(pages[0]);
}
/**
* Method to navigate to the error page
*
*/
gotoErrorPage(errorResponse?: HttpErrorResponse) {
this.pfeBusinessService.navigateToErrorPage(errorResponse);
}
saveOffer(): void {
this.actionsService.executeAction({
type: 'TALY_SAVE_OFFER'
});
}
cancel(): void {
this.actionsService.executeAction({
type: 'TALY_CANCEL_OPERATION'
});
}
getOfferCode$(stateKey: string): Observable<string | undefined> {
return this.pfeBusinessService.getObservableForExpressionKey(stateKey, true);
}
initApplicationBusy$(): void {
const busy$: Observable<boolean> = combineLatest([
this.pfeServiceActivatorService.serviceActivatorCallInProgress$,
this.pfeNavigationService.navigationInProgress$
]).pipe(
map(
([serviceActivatorCallInProgress, navigationInProgress]) =>
serviceActivatorCallInProgress || navigationInProgress
),
distinctUntilChanged()
);
this.applicationBusy$ = busy$.pipe(
debounce((busy) => (busy ? timer(this.APPLICATION_BUSY_DELAY) : timer(0)))
);
this.initApplicationBusyMessage$(busy$);
}
initApplicationBusyMessage$(busy$: Observable<boolean>): void {
const message$ = this.pfeServiceActivatorService.serviceActivatorCallInProgressDetails$.pipe(
filter((configs: ServiceActivatorProgressDetails[]) => Boolean(configs?.length)),
map(
(configs: ServiceActivatorProgressDetails[]) =>
// If multiple service activators are running at the same time
// Use the first one that has displayMessage config
configs.find((config) => config.displayMessage)?.displayMessage
)
);
// Emit a new message every 1 second
const minimumDisplay$ = timer(0, this.MINIMUM_MESSAGE_TIME).pipe(
switchMap(() => message$.pipe(take(1)))
);
// Switch a message if it's been displayed longer than 1 second
const messageSwitcher$ = message$.pipe(
timeInterval(),
filter(({ interval }) => interval > this.MINIMUM_MESSAGE_TIME),
map(({ value }) => value)
);
const applicationBusyMessage$ = merge(minimumDisplay$, messageSwitcher$).pipe(
distinctUntilChanged()
);
this.applicationBusyMessage$ = busy$.pipe(
switchMap((isBusy) => {
if (isBusy) {
return applicationBusyMessage$;
}
return EMPTY;
})
);
}
protected getVisibleSections$(sections: SectionConfig): Observable<SectionConfig> {
const conditionalSections = Array.from(sections).filter(
([, section]) => section.visibleIf !== undefined
);
if (conditionalSections.length === 0) {
return of(sections);
}
const conditions$: Observable<[sectionId: string, stateValue: boolean]>[] =
conditionalSections.map(([sectionId, section]) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (JSON_PATH_REGEX.test(section.visibleIf!)) {
return this.createSectionObservableWithPfeExpressionCondition(
sectionId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
section.visibleIf!
);
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.createSectionObservableWithJsonPathExpression(sectionId, section.visibleIf!);
}
});
return combineLatest(conditions$).pipe(
map((conditions) => {
const conditionMap = new Map(conditions);
return filterSections(sections, conditionMap);
})
);
}
private createSectionObservableWithPfeExpressionCondition(
sectionId: string,
visibleIf: string
): Observable<[sectionId: string, stateValue: boolean]> {
const condition: ExpressionCondition[] = [{ expression: visibleIf }];
// get all property keys which are in curly brackets (e.g. "{key1} == {key2}" => ["key1", "key2"])
const propertyKeys = visibleIf.match(JSON_PATH_REGEX)?.map((match) => match.slice(1, -1)) || [];
return merge(
...propertyKeys.map((key) => this.pfeBusinessService.getObservableForExpressionKey(key, true))
).pipe(
map(() => [
sectionId,
this.pfeConditionsService.evaluateConditions(condition, this.pfeStateService.getFullState())
])
);
}
private createSectionObservableWithJsonPathExpression(
sectionId: string,
visibleIf: string
): Observable<[sectionId: string, stateValue: boolean]> {
return this.pfeBusinessService
.getObservableForExpressionKey(visibleIf, true)
.pipe(map((stateValue) => [sectionId, Boolean(stateValue)]));
}
}
function createPfeHistoryObservable(pfeStateService: PfeStateService): Observable<PageHistory> {
return pfeStateService.getObservableForKey(PFE_HISTORY_KEY).pipe(
debounceTime(50),
map((history) => {
let currentPage = null;
if (history.length > 0) {
currentPage = history[history.length - 1];
}
return { history, currentPage };
})
);
}
function filterSections(
allSections: SectionConfig,
conditions: Map<string, boolean>
): SectionConfig {
return new Map(
Array.from(allSections).filter(([sectionId]) => {
const hasCondition = conditions.has(sectionId);
const conditionIsTrue = conditions.get(sectionId) === true;
return !hasCondition || conditionIsTrue;
})
);
}