/**
* @module lib/engine/NgxPageDirective
* @description Base page component for Decaf Angular applications.
* @summary Provides a page-level base class (NgxPageDirective) that extends NgxComponentDirective and
* offers page-focused utilities such as menu management, title handling and router event hooks.
* @link {@link NgxPageDirective}
*/
import { AfterViewInit, Directive, Inject, inject } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { NavigationEnd, NavigationStart } from '@angular/router';
import { MenuController } from '@ionic/angular';
import { Model } from '@decaf-ts/decorator-validation';
import { IMenuItem } from './interfaces';
import { CPTKN } from './constants';
import { removeFocusTrap } from '../utils/helpers';
import { KeyValue } from './types';
import { NgxComponentDirective } from './NgxComponentDirective';
import { shareReplay, takeUntil } from 'rxjs';
/**
* @description Base directive for page-level components in Decaf Angular applications.
* @summary Abstract directive that provides foundational functionality for page components.
* Extends NgxComponentDirective to add page-specific features including menu management,
* page title handling, and Ionic lifecycle hooks. This directive serves as the base class for
* all page-level components, providing consistent behavior for navigation, routing, and UI state.
* @class NgxPageDirective
* @extends {NgxComponentDirective}
* @memberOf module:lib/engine/NgxPageDirective
*/
@Directive()
export abstract class NgxPageDirective extends NgxComponentDirective implements AfterViewInit {
/**
* @description Application name for display or identification purposes.
* @summary Stores the name of the application, which can be used for display in headers,
* titles, or for logging and identification throughout the app.
* @type {string}
* @memberOf module:lib/engine/NgxPageDirective
*/
appName?: string;
/**
* @description Page title text for the current view.
* @summary Stores the title text to be displayed for this page. This can be set dynamically
* based on the current route or menu configuration and is used to update the browser's
* title bar or page header.
* @type {string}
* @default ''
* @memberOf module:lib/engine/NgxPageDirective
*/
title: string = '';
/**
* @description Global key-value pairs for application-wide settings.
* @summary This property stores global configuration values that can be accessed
* throughout the application. It is typically used to manage shared state or
* settings that are relevant across multiple components or services.
* @type {KeyValue}
* @memberOf module:lib/engine/NgxPageDirective
*/
globals!: KeyValue;
/**
* @description Ionic menu controller service for menu management.
* @summary Injected service that provides programmatic control over Ionic menu components.
* This service allows the component to open, close, toggle, and manage menu states within
* the application. It provides access to menu functionality for implementing navigation
* and layout features that require menu interaction.
* @protected
* @type {MenuController}
* @memberOf module:lib/engine/NgxPageDirective
*/
protected menuController: MenuController = inject(MenuController);
/**
* @description Menu items array for page navigation.
* @summary Contains the collection of menu items available for this page. Each menu item
* defines a navigation option with properties like label, URL, icon, and visibility settings.
* This array is used to construct the application's navigation menu and can be filtered or
* customized per page.
* @protected
* @type {IMenuItem[]}
* @default []
* @memberOf module:lib/engine/NgxPageDirective
*/
protected menu: IMenuItem[] = [];
/**
* @description Angular Title service for browser title management.
* @summary Injected service that provides control over the browser's document title.
* Used to dynamically set the page title based on the current route or active menu item,
* improving SEO and user experience.
* @protected
* @type {Title}
* @memberOf module:lib/engine/NgxPageDirective
*/
protected titleService: Title = inject(Title);
/**
* @description Flag indicating whether the page should display the navigation menu.
* @summary Controls the visibility and availability of the application menu for this page.
* When set to true, the menu is enabled and accessible to users. When false, the menu
* is disabled, which is useful for pages like login screens or standalone views that
* should not show navigation options.
* @protected
* @type {boolean}
* @default true
* @memberOf module:lib/engine/NgxPageDirective
*/
protected hasMenu: boolean = true;
/**
* @description Currently active route path for the page (without leading slash).
* @summary This protected property stores the current route segment used by the
* page to determine the displayed title and menu state. It is updated on
* NavigationEnd events (see ngAfterViewInit) and consumed by the `pageTitle`
* getter and `setPageTitle()` method. When undefined the component will
* attempt to resolve the route from the router URL.
* @protected
* @type {string | undefined}
* @default undefined
*/
protected currentRoute?: string;
/**
* @description Constructor for NgxPageDirective.
* @summary Initializes the page directive with optional locale root and menu visibility settings.
* Calls the parent NgxComponentDirective constructor to set up base functionality including
* logging, localization, and component identification.
* @param {string} [localeRoot] - Optional locale root key for internationalization
* @param {boolean} [hasMenu=true] - Whether this page should display the menu
* @memberOf module:lib/engine/NgxPageDirective
*/
constructor(
// eslint-disable-next-line @angular-eslint/prefer-inject
@Inject(CPTKN) localeRoot: string = 'NgxPageDirective',
// eslint-disable-next-line @angular-eslint/prefer-inject
@Inject(CPTKN) hasMenu: boolean = true,
) {
super(localeRoot);
this.hasMenu = hasMenu;
// subscribe to media service color scheme changes
}
get pageTitle(): string {
if (this.title) return this.title;
if (this.locale) return `${this.locale}.title`;
if (this.currentRoute) return `${this.currentRoute}.title`;
return `${(this.model as Model).constructor.name}.title`;
}
// async ngOnInit(): Promise<void> {}
override async initialize(): Promise<void> {
await super.initialize();
// connect component to media service for color scheme toggling
this.currentRoute = this.router.url.replace('/', '');
if (!this.route) this.route = this.currentRoute;
this.setPageTitle(this.currentRoute);
}
/**
* @description Ionic lifecycle hook called when the page is about to enter view.
* @summary This lifecycle hook is triggered just before the page becomes visible to the user.
* It enables or disables the application menu based on the hasMenu property, allowing pages
* to control whether the menu should be accessible. This is useful for pages like login screens
* where the menu should be hidden.
* @return {Promise<void>} A promise that resolves when menu state is updated
* @memberOf module:lib/engine/NgxPageDirective
*/
async ngAfterViewInit(): Promise<void> {
this.router.events
.pipe(takeUntil(this.destroySubscriptions$), shareReplay(1))
.subscribe(async (event) => {
if (event instanceof NavigationEnd) {
const url = (event?.url || '').replace('/', '');
this.currentRoute = url;
if (this.hasMenu) this.hasMenu = url !== 'login' && url !== '';
this.title = this.pageTitle;
await this.setPageTitle(url);
this.changeDetectorRef.detectChanges();
}
if (event instanceof NavigationStart) {
const url = (event?.url || '').replace('/', '');
if (this.hasMenu) this.hasMenu = url !== 'login' && url !== '';
removeFocusTrap();
}
});
if (!this.route) this.route = this.router.url.replace('/', '');
await this.menuController.enable(this.hasMenu);
}
/**
* @description Sets the browser page title based on the current route.
* @summary Updates the browser's document title by finding the active menu item that matches
* the provided route. If a matching menu item is found, it sets the title using the format
* "Decaf For Angular - {menu title or label}". This improves SEO and provides clear context
* to users about the current page. If a custom menu array is provided, it uses that instead
* of the component's default menu.
* @protected
* @param {string} route - The current route path to match against menu items
* @param {IMenuItem[]} [menu] - Optional custom menu array to search (uses this.menu if not provided)
* @return {void}
* @memberOf module:lib/engine/NgxPageDirective
*/
protected async setPageTitle(route?: string, menu?: IMenuItem[]): Promise<void> {
if (!route) route = this.router.url.replace('/', '');
if (!menu) menu = this.menu;
const activeMenu = menu.find((item) => item?.url?.includes(route));
if (activeMenu) {
const label = `${(activeMenu?.title || activeMenu?.label || '').toLowerCase()}`;
const title = `${await this.translate(label ? 'menu.' + label : label)} ${this.appName ? `- ${this.appName}` : ''}`;
this.titleService.setTitle(title);
if (!this.title) this.title = title;
}
}
}
Source