From b7f31137d673665b5732020829d53afd3d11e30e Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Wed, 18 Sep 2024 14:23:11 -0700 Subject: [PATCH 01/12] refactor: header component --- .../components/header/header.component.html | 21 ++- .../components/header/header.component.ts | 137 +++++++++--------- 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/src/app/shared/components/header/header.component.html b/src/app/shared/components/header/header.component.html index 4211224799..a66a230875 100644 --- a/src/app/shared/components/header/header.component.html +++ b/src/app/shared/components/header/header.component.html @@ -1,17 +1,26 @@ - - + + - + @if (showMenuButton()) { + + } - - {{ headerConfig.title }} + + {{ headerConfig().title }} diff --git a/src/app/shared/components/header/header.component.ts b/src/app/shared/components/header/header.component.ts index 755835bd17..5ce984e9f1 100644 --- a/src/app/shared/components/header/header.component.ts +++ b/src/app/shared/components/header/header.component.ts @@ -1,14 +1,12 @@ import { Location } from "@angular/common"; -import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { computed, effect, signal } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; import { NavigationEnd, NavigationStart, Router } from "@angular/router"; import { App } from "@capacitor/app"; import { Capacitor, PluginListenerHandle } from "@capacitor/core"; import { Subscription, fromEvent, map } from "rxjs"; -import type { - IAppConfig, - IHeaderColourOptions, - IHeaderVariantOptions, -} from "data-models/appConfig"; +import type { IHeaderVariantOptions } from "data-models/appConfig"; import { AppConfigService } from "../../services/app-config/app-config.service"; import { IonHeader, ScrollBaseCustomEvent, ScrollDetail } from "@ionic/angular"; import { _wait } from "packages/shared/src/utils/async-utils"; @@ -27,24 +25,28 @@ const heightsMap = { selector: "plh-main-header", templateUrl: "./header.component.html", styleUrls: ["./header.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class headerComponent implements OnInit, OnDestroy { @ViewChild(IonHeader) headerElement: IonHeader & { el: HTMLElement }; - showMenuButton = false; - showBackButton = false; - appConfigChanges$: Subscription; - headerConfig: IAppConfig["APP_HEADER_DEFAULTS"]; - routeChanges$: Subscription; + showMenuButton = signal(false); + showBackButton = signal(false); + headerConfig = computed(() => this.appConfigService.appConfig().APP_HEADER_DEFAULTS); + + /** + * Listen to router events to handle route change effects + * NOTE - use events instead of route as header sits outside ion-router (limited access) + **/ + private routeChanges = toSignal(this.router.events); + /** listen to hardware back button presses (on android device only) */ - hardwareBackButton$: PluginListenerHandle; + private hardwareBackButton$: PluginListenerHandle; /** track if navigation has been used to handle back button click behaviour */ - hasBackHistory = false; - colour: IHeaderColourOptions; - variant: IHeaderVariantOptions; + private hasBackHistory = false; /** Modify margin to move off-screen when using collapsed mode */ - public marginTop = "0px"; + public marginTop = signal(0); /** Track scroll events when using header collapse mode */ private scrollEvents$: Subscription; @@ -53,19 +55,35 @@ export class headerComponent implements OnInit, OnDestroy { private router: Router, private location: Location, private appConfigService: AppConfigService - ) {} + ) { + effect( + () => { + // when header config changes set the height and collapse properties + const { collapse, variant } = this.headerConfig(); + this.setHeaderHeightCSS(variant); + if (collapse !== undefined) { + this.setHeaderCollapse(collapse); + } + }, + { allowSignalWrites: true } + ); + effect( + () => { + // when route changes handle side-effects + const e = this.routeChanges(); + if (e instanceof NavigationEnd) { + this.handleRouteChange(); + } + if (e instanceof NavigationStart) { + this.hasBackHistory = true; + } + }, + { allowSignalWrites: true } + ); + } async ngOnInit() { - this.subscribeToAppConfigChanges(); - // subscribe to and handle route changes - this.routeChanges$ = this.router.events.subscribe((e) => { - if (e instanceof NavigationEnd) { - this.handleRouteChange(); - } - if (e instanceof NavigationStart) { - this.hasBackHistory = true; - } - }); + // trigger route change for initial configuration this.handleRouteChange(); // subscribe to and handle hardware back button press if (Capacitor.getPlatform() === "android") { @@ -73,24 +91,6 @@ export class headerComponent implements OnInit, OnDestroy { this.handleHardwareBackPress(); }); } - // HACK - uncomment to test collapse - // this.appConfigService.setAppConfig({ APP_HEADER_DEFAULTS: { collapse: true } }); - } - - subscribeToAppConfigChanges() { - this.appConfigChanges$ = this.appConfigService.changesWithInitialValue$.subscribe( - (changes: IAppConfig) => { - if (changes.APP_HEADER_DEFAULTS) { - const headerConfig = this.appConfigService.appConfig().APP_HEADER_DEFAULTS; - this.headerConfig = headerConfig; - this.updateHeaderConfig(); - // handle collapse config changes - if (changes.APP_HEADER_DEFAULTS?.collapse !== undefined) { - this.handleHeaderCollapseConfigChange(changes.APP_HEADER_DEFAULTS?.collapse); - } - } - } - ); } public handleBackButtonClick() { @@ -101,54 +101,48 @@ export class headerComponent implements OnInit, OnDestroy { } } - /** - * Optional methods that can respond to route changes from within the header component - * It cannot subscribe to standard router methods as sits outside ion-router-outlet - */ + /** Determine whether to show back and menu buttons based on location */ private handleRouteChange() { - const { should_show_back_button, should_show_menu_button } = this.headerConfig; - this.showBackButton = should_show_back_button(location); - this.showMenuButton = should_show_menu_button(location); - this.marginTop = "0px"; + const { should_show_back_button, should_show_menu_button } = this.headerConfig(); + this.showBackButton.set(should_show_back_button(location)); + this.showMenuButton.set(should_show_menu_button(location)); + this.marginTop.set(0); } /** When device back button evaluate conditions to handle app minimise */ private handleHardwareBackPress() { - const { should_minimize_app_on_back } = this.headerConfig; + const { should_minimize_app_on_back } = this.headerConfig(); if (should_minimize_app_on_back(location)) { App.minimizeApp(); } } - private updateHeaderConfig() { - this.colour = this.headerConfig.colour; - const { variant } = this.headerConfig; - this.variant = variant; + private setHeaderHeightCSS(variant: IHeaderVariantOptions) { // Set CSS property dynamically in component so that it can be exposed to deployment config/skins - document.documentElement.style.setProperty( - "--header-height", - `${heightsMap[this.variant] || heightsMap.default}px` - ); + const targetHeight = heightsMap[variant] || heightsMap.default; + document.documentElement.style.setProperty("--header-height", `${targetHeight}px`); } /** * When enabling header collapse listen add scroll event listeners to detect when to collapse * If disabling dispose of previous scroll event listeners */ - private handleHeaderCollapseConfigChange(shouldCollapse: boolean) { + private setHeaderCollapse(shouldCollapse: boolean) { // TODO: current header collapse implementation does not work on ios, so do not enable on this platform if (Capacitor.getPlatform() === "ios") return; + this.removeScrollEventListeners(); + if (shouldCollapse) { + this.listenToScrollEvents(); + } + } + private removeScrollEventListeners() { // If previously scroll events were subscribed then should be able to unsubscribe. // If initial config change undefined->false can ignore if (this.scrollEvents$) { this.scrollEvents$.unsubscribe(); this.scrollEvents$ = undefined; - this.marginTop = "0px"; - // unset height - } - if (shouldCollapse) { - this.listenToScrollEvents(); + this.marginTop.set(0); } } @@ -161,7 +155,7 @@ export class headerComponent implements OnInit, OnDestroy { const { deltaY, currentY } = detail; // bring header into view when scrolling in reverse or at top of page if (deltaY <= 0 || currentY < headerHeight) { - this.marginTop = "0px"; + this.marginTop.set(0); } else { // hide header after scrolled further than header height if (currentY >= headerHeight) { @@ -169,7 +163,7 @@ export class headerComponent implements OnInit, OnDestroy { // as that would trigger negative scroll direction this.scrollEvents$.unsubscribe(); // set negative margin to move header off-screen - this.marginTop = `-${headerHeight}px`; + this.marginTop.set(headerHeight * -1); await _wait(500); this.listenToScrollEvents(); } @@ -178,8 +172,7 @@ export class headerComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.appConfigChanges$.unsubscribe(); - this.routeChanges$.unsubscribe(); + this.removeScrollEventListeners(); if (this.hardwareBackButton$) { this.hardwareBackButton$.remove(); } From f357220ba119e1642bee6257ad8fb05f001634f6 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Wed, 18 Sep 2024 14:33:46 -0700 Subject: [PATCH 02/12] refactor: app component --- src/app/app.component.html | 124 ++++++++++++++++++------------------- src/app/app.component.ts | 34 +++------- 2 files changed, 71 insertions(+), 87 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 27bacab8d8..1b16ac041d 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,67 +1,67 @@ - - - - - - {{ sideMenuDefaults.title }} -
- @if (sideMenuDefaults.should_show_version) { - @if (deploymentConfig._content_version; as CONTENT_VERSION) { - - - {{ CONTENT_VERSION }} - - - } @else { - {{ deploymentConfig._app_builder_version }} - } - } - ({{ deploymentConfig.name }}) -
-
-
- - - -
- - -
- -
- -
- - + + @if (sideMenuDefaults().enabled) { + + + + {{ sideMenuDefaults().title }} +
+ @if (sideMenuDefaults().should_show_version) { + @if (deploymentConfig._content_version; as CONTENT_VERSION) { + + + {{ CONTENT_VERSION }} + + + } @else { + {{ deploymentConfig._app_builder_version }} + } + } + @if (sideMenuDefaults().should_show_deployment_name) { + ({{ deploymentConfig.name }}) + } +
+
+
+ -
-
-
- - -