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
errorMessage
|
Type : string | undefined
|
|
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, inject } 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 {
private pfeBusinessService = inject(PfeBusinessService);
private pfeNavigationService = inject(PfeNavigationService);
private pfeStateService = inject(PfeStateService);
private pfeConfigService = inject(PfeConfigurationService);
private pfeServiceActivatorService = inject(PfeServiceActivatorService);
private pfeUtilService = inject(PfeUtilService);
private pfeConditionsService = inject(PfeConditionsService);
private cdr = inject(ChangeDetectorRef);
private elementRef = inject(ElementRef);
private router = inject(Router);
// 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
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$.pipe(takeUntil(this.unSubscribeSubject$)).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$.pipe(takeUntil(this.unSubscribeSubject$)).subscribe((status) => {
this.pageStatus = status;
this.cdr.detectChanges();
});
this.pfeBusinessService.busy$.pipe(takeUntil(this.unSubscribeSubject$)).subscribe((busy) => {
this.navigationOrServiceActivatorInProgress = busy;
});
this.pfeBusinessService.errorMessage$.pipe(takeUntil(this.unSubscribeSubject$)).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();
}
}
@if (headerTemplate) {
<div>
<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>
}
@if (errorMessageTemplate) {
<div>
<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>
}
@if (noConfigMessageTemplate) {
<div>
<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>
}
@if (pageOutlet) {
<div>
<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>
}
@if (navigationTemplate) {
<div [@.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>
}
@if (footerTemplate) {
<div>
<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