File
Description
The master page is the central PFE component.
All pages are displayed within this component.
Implements
Index
Properties
|
|
Methods
|
|
Inputs
|
|
Methods
Async
backClicked
|
backClicked()
|
|
|
Async
nextClicked
|
nextClicked(nextClickedCallback: () => void)
|
|
Parameters :
Name |
Type |
Optional |
nextClickedCallback |
function
|
No
|
|
Public
backButtonLabel
|
Type : string
|
|
Public
errorMessageTemplate
|
Type : TemplateRef<> | null
|
Default value : null
|
Decorators :
@ContentChild('errorMessageTemplate', {read: TemplateRef, static: true})
|
|
Public
footerTemplate
|
Type : TemplateRef<> | null
|
Default value : null
|
Decorators :
@ContentChild('footerTemplate', {read: TemplateRef, static: true})
|
|
Public
headerTemplate
|
Type : TemplateRef<> | null
|
Default value : null
|
Decorators :
@ContentChild('headerTemplate', {read: TemplateRef, static: true})
|
|
Public
labelCondition
|
Default value : false
|
|
Public
navigateBackCallback
|
Type : function
|
|
Public
navigateNextCallback
|
Type : function
|
|
Public
navigationOrServiceActivatorInProgress
|
Default value : false
|
|
Flag, that is set to true, while a navigation or service activator call is active.
|
Public
navigationTemplate
|
Type : TemplateRef<> | null
|
Default value : null
|
Decorators :
@ContentChild('navigationTemplate', {read: TemplateRef, static: true})
|
|
References to templates passed as ng-content
|
Public
nextButtonLabel
|
Type : string
|
|
Public
noConfig
|
Default value : false
|
|
Public
noConfigMessageTemplate
|
Type : TemplateRef<> | null
|
Default value : null
|
Decorators :
@ContentChild('noConfigMessageTemplate', {read: TemplateRef, static: true})
|
|
Public
pageOutlet
|
Type : TemplateRef<> | null
|
Default value : null
|
Decorators :
@ContentChild('pageOutlet', {read: TemplateRef, static: true})
|
|
Public
pageStatus
|
Default value : false
|
|
The pageStatus determines if the page is valid.
It is used, to prevent navigation in certain cases.
For example, when a form is not filled out completely.
|
Public
subscriptions
|
Type : Subscription
|
|
import { ChangeDetectorRef, Component, ContentChild, ElementRef, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { Router } from '@angular/router';
import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { fadeIn } from '../../animations/animations';
import { AppConfiguration, PfeConfig } from '../../models/app-configuration.model';
import { PageConfig } from '../../models/ngx-pfe-page-config.model';
import { PfeConditionsService, PfeRulesEvaluator } from '../../pfe-conditions/public-api';
import { PfeServiceActivatorService } from '../../pfe-service-activator/service/service-activator.service';
import { PfeUtilService } from '../../pfe-util/services/util.service';
import { PfeBusinessService } from '../../services/pfe-business-service/business.service';
import { PfeConfigurationService } from '../../services/pfe-config-service/config-service.service';
import { PfeNavigationService } from '../../services/pfe-navigation-service/navigation.service';
import { PfeStateService } from '../../services/pfe-state-service/state.service';
/**
* The master page is the central PFE component.
* All pages are displayed within this component.
*
* @export
*/
@Component({
selector: 'pfe-master-page',
templateUrl: 'master-page.component.html',
styleUrls: ['./master-page.component.scss'],
animations: [fadeIn],
standalone: false,
})
export class PfeMasterPageComponent implements OnInit, OnDestroy {
// Unfortunately, the template context has to be provided in the template as separate objects.
/**
* References to templates passed as ng-content
*/
@ContentChild('navigationTemplate', { read: TemplateRef, static: true })
public navigationTemplate: TemplateRef<unknown> | null = null;
@ContentChild('errorMessageTemplate', { read: TemplateRef, static: true })
public errorMessageTemplate: TemplateRef<unknown> | null = null;
@ContentChild('noConfigMessageTemplate', { read: TemplateRef, static: true })
public noConfigMessageTemplate: TemplateRef<unknown> | null = null;
@ContentChild('pageOutlet', { read: TemplateRef, static: true })
public pageOutlet: TemplateRef<unknown> | null = null;
@ContentChild('headerTemplate', { read: TemplateRef, static: true })
public headerTemplate: TemplateRef<unknown> | null = null;
@ContentChild('footerTemplate', { read: TemplateRef, static: true })
public footerTemplate: TemplateRef<unknown> | null = null;
@Input() nextClickedCallback!: () => Promise<boolean>;
// Only to be accessed by the template:
pageConfig: PageConfig | undefined;
appConfiguration!: AppConfiguration | undefined;
public navigateCallback!: (nextPage: string) => void;
public navigateNextCallback!: () => void;
public navigateBackCallback!: () => void;
public nextButtonLabel!: string;
public backButtonLabel!: string;
/**
* The pageStatus determines if the page is valid.
* It is used, to prevent navigation in certain cases.
* For example, when a form is not filled out completely.
*/
public pageStatus = false;
// TODO: Add proper no config error handling:
public noConfig = false;
public subscriptions!: Subscription;
/**
* Flag, that is set to true, while a navigation or service activator call is active.
*/
public navigationOrServiceActivatorInProgress = false;
public labelCondition = false;
public errorMessage: string | undefined;
private errorState = false;
private componentDestroyed$ = new Subject<void>();
private unSubscribeSubject$ = new Subject<void>();
// TODO: add template (=could be xtra error page with hardcoded config) and default for generic error page, that works without config
constructor(
private pfeBusinessService: PfeBusinessService,
private pfeNavigationService: PfeNavigationService,
private pfeStateService: PfeStateService,
private pfeConfigService: PfeConfigurationService,
private pfeServiceActivatorService: PfeServiceActivatorService,
private pfeUtilService: PfeUtilService,
private pfeConditionsService: PfeConditionsService,
private cdr: ChangeDetectorRef,
private elementRef: ElementRef,
private router: Router
) {}
public ngOnInit() {
this.navigateCallback = this.navigate.bind(this);
this.navigateNextCallback = this.nextClicked.bind(this, this.nextClickedCallback);
this.navigateBackCallback = this.backClicked.bind(this);
this.pfeNavigationService.currentPageId$.subscribe((pageID) => {
(async () => {
this.pageConfig = await this.pfeConfigService.getPageConfiguration(pageID);
if (this.pageConfig) {
this.updatePageStatus(this.pageConfig);
}
})();
});
// Keep the new pageStatus subject state in sync for the master page:
this.pfeNavigationService.pageStatus$.subscribe((status) => {
this.pageStatus = status;
this.cdr.detectChanges();
});
this.pfeBusinessService.busy$.subscribe((busy) => {
this.navigationOrServiceActivatorInProgress = busy;
});
this.pfeBusinessService.errorMessage$.subscribe((message) => {
this.errorMessage = message;
setTimeout(() => {
this.elementRef?.nativeElement?.scrollIntoView?.({ block: 'start', behavior: 'smooth' });
});
});
(async () => {
try {
if (!(await this.pfeBusinessService.hasConfig())) {
this.noConfig = true;
this.errorState = true;
// TODO: Show generic error page, that works without configuration, should be done in config service.
// this.currentPageID = await this.pfeBusinessService.getErrorPage();
}
this.appConfiguration = await this.pfeConfigService.getAppConfiguration();
this.initializeMasterPageAppConfiguration(this.appConfiguration?.pfeConfig);
} catch (error) {
this.noConfig = true;
this.errorState = true;
throw error;
}
})();
}
async nextClicked(nextClickedCallback: () => Promise<boolean>) {
if (nextClickedCallback) {
const canNavigate = await nextClickedCallback();
if (canNavigate) {
this.nextClickedNavigate();
}
} else {
this.nextClickedNavigate();
}
}
async backClicked() {
this.pfeUtilService.trackingEvents$.next({ event: 'navigationButton', value: 'BACK_BUTTON' });
if (this.errorState) {
return;
}
await this.pfeNavigationService.navigateBack();
}
public ngOnDestroy() {
this.unSubscribeSubject$.next();
this.unSubscribeSubject$.complete();
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
}
private checkChangeTextButton(pageConfig: PageConfig) {
this.labelCondition = false;
const btnCondition = pageConfig.nextBtnOptionalLabelCondition;
if (btnCondition) {
const propertyKeys = PfeRulesEvaluator.getStatePropertyName(btnCondition);
propertyKeys.forEach((key) => {
this.pfeBusinessService
.getObservableForKey(key)
.pipe(takeUntil(this.unSubscribeSubject$))
.subscribe(() => {
const result = this.pfeConditionsService.evaluateConditions(btnCondition, this.pfeStateService.getFullState());
if (result !== this.labelCondition) {
this.labelCondition = result;
this.cdr.detectChanges();
}
});
});
}
}
private initializeMasterPageAppConfiguration(pfeConfig: PfeConfig | undefined) {
if (pfeConfig) {
this.nextButtonLabel = pfeConfig.nextButtonLabel ?? 'next_button';
this.backButtonLabel = pfeConfig.backButtonLabel ?? 'back_button';
}
}
/**
* Triggered by changes in the navigation.
*/
private updatePageStatus(pageConfig: PageConfig) {
this.unSubscribeSubject$.next();
this.checkChangeTextButton(pageConfig);
}
private async navigate(nextPage: string) {
await this.pfeNavigationService.navigate(nextPage);
}
private async nextClickedNavigate() {
this.pfeUtilService.trackingEvents$.next({ event: 'navigationButton', value: 'NEXT_BUTTON' });
if (this.errorState) {
return;
}
await this.pfeNavigationService.navigateNext();
}
}
<div *ngIf="headerTemplate">
<ng-container
*ngTemplateOutlet="
headerTemplate;
context: {
appConfiguration: appConfiguration,
pageConfig: pageConfig,
nextButtonLabel: nextButtonLabel,
backButtonLabel: backButtonLabel,
navigate: navigateCallback,
navigateNext: navigateNextCallback,
navigateBack: navigateBackCallback,
labelCondition: labelCondition,
pageStatus: pageStatus,
errorMessage: errorMessage,
noConfig: noConfig,
navigationOrServiceActivatorInProgress: navigationOrServiceActivatorInProgress
}
"
></ng-container>
</div>
<div *ngIf="errorMessageTemplate">
<ng-container
*ngTemplateOutlet="
errorMessageTemplate;
context: {
appConfiguration: appConfiguration,
pageConfig: pageConfig,
nextButtonLabel: nextButtonLabel,
backButtonLabel: backButtonLabel,
navigate: navigateCallback,
navigateNext: navigateNextCallback,
navigateBack: navigateBackCallback,
labelCondition: labelCondition,
pageStatus: pageStatus,
errorMessage: errorMessage,
noConfig: noConfig,
navigationOrServiceActivatorInProgress: navigationOrServiceActivatorInProgress
}
"
></ng-container>
</div>
<div *ngIf="noConfigMessageTemplate">
<ng-container
*ngTemplateOutlet="
noConfigMessageTemplate;
context: {
appConfiguration: appConfiguration,
pageConfig: pageConfig,
nextButtonLabel: nextButtonLabel,
backButtonLabel: backButtonLabel,
navigate: navigateCallback,
navigateNext: navigateNextCallback,
navigateBack: navigateBackCallback,
labelCondition: labelCondition,
pageStatus: pageStatus,
errorMessage: errorMessage,
noConfig: noConfig,
navigationOrServiceActivatorInProgress: navigationOrServiceActivatorInProgress
}
"
></ng-container>
</div>
<div *ngIf="pageOutlet">
<ng-container
*ngTemplateOutlet="
pageOutlet;
context: {
appConfiguration: appConfiguration,
pageConfig: pageConfig,
nextButtonLabel: nextButtonLabel,
backButtonLabel: backButtonLabel,
navigate: navigateCallback,
navigateNext: navigateNextCallback,
navigateBack: navigateBackCallback,
labelCondition: labelCondition,
pageStatus: pageStatus,
errorMessage: errorMessage,
noConfig: noConfig,
navigationOrServiceActivatorInProgress: navigationOrServiceActivatorInProgress
}
"
></ng-container>
</div>
<div *ngIf="navigationTemplate" [@.disabled]="!!pageConfig?.disableAnimations" [@routeAnimations]="pageConfig?.pageId">
<ng-container
*ngTemplateOutlet="
navigationTemplate;
context: {
appConfiguration: appConfiguration,
pageConfig: pageConfig,
nextButtonLabel: nextButtonLabel,
backButtonLabel: backButtonLabel,
navigate: navigateCallback,
navigateNext: navigateNextCallback,
navigateBack: navigateBackCallback,
labelCondition: labelCondition,
pageStatus: pageStatus,
errorMessage: errorMessage,
noConfig: noConfig,
navigationOrServiceActivatorInProgress: navigationOrServiceActivatorInProgress
}
"
></ng-container>
</div>
<div *ngIf="footerTemplate">
<ng-container
*ngTemplateOutlet="
footerTemplate;
context: {
appConfiguration: appConfiguration,
pageConfig: pageConfig,
nextButtonLabel: nextButtonLabel,
backButtonLabel: backButtonLabel,
navigate: navigateCallback,
navigateNext: navigateNextCallback,
navigateBack: navigateBackCallback,
labelCondition: labelCondition,
pageStatus: pageStatus,
errorMessage: errorMessage,
noConfig: noConfig,
navigationOrServiceActivatorInProgress: navigationOrServiceActivatorInProgress
}
"
></ng-container>
</div>
.master-page {
padding-bottom: 20px;
}
Legend
Html element with directive