From abee5a4974ecf00e92593f5b93e46ff1ed38781b Mon Sep 17 00:00:00 2001 From: Glen Staes Date: Mon, 13 Apr 2026 19:58:58 +0200 Subject: [PATCH 1/3] Extend wireframe component to allow hiding the toolbar based on route data --- projects/ppwcode/ng-wireframe/README.md | 3 +- .../lib/wireframe/wireframe.component.html | 4 +- .../lib/wireframe/wireframe.component.spec.ts | 69 +++++++++++++++++++ .../src/lib/wireframe/wireframe.component.ts | 24 ++++--- 4 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts diff --git a/projects/ppwcode/ng-wireframe/README.md b/projects/ppwcode/ng-wireframe/README.md index d0da10cb..f732062a 100644 --- a/projects/ppwcode/ng-wireframe/README.md +++ b/projects/ppwcode/ng-wireframe/README.md @@ -55,7 +55,8 @@ The following CSS variables are available for theming. Just add them to the `bod ### Wireframe The wireframe places an application layout with responsive left navigation and a toolbar on your web page. -You can hide the wireframe by adding a flag in the route configuration - In data add the `'showWireframe: false'` flag - If this flag is not added it will by default be true and show the wireframe +You can hide the wireframe entirely by adding the `showWireframe: false` flag to the route data. If this flag is not added, the wireframe is shown by default. +You can hide the toolbar while keeping the wireframe visible by adding the `showToolbar: false` flag to the route data. If this flag is not added, the toolbar is shown by default. #### Parameters diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html index cc971a50..121090ed 100644 --- a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html +++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html @@ -35,7 +35,7 @@ " >
- @if (showWireframe()) { + @if (showWireframe() && showToolbar()) { } - @if (showBreadcrumb()) { + @if (showBreadcrumb() && showToolbar()) { diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts new file mode 100644 index 00000000..a5be060f --- /dev/null +++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts @@ -0,0 +1,69 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { provideRouter, Router } from '@angular/router' +import { provideTranslateService } from '@ngx-translate/core' +import { WireframeComponent } from './wireframe.component' + +@Component({ + template: '', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush +}) +class VisibleRouteComponent {} + +@Component({ + template: '', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush +}) +class HiddenToolbarRouteComponent {} + +describe('WireframeComponent', () => { + let component: WireframeComponent + let fixture: ComponentFixture + let router: Router + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WireframeComponent], + providers: [ + provideTranslateService({}), + provideRouter([ + { + path: 'visible', + component: VisibleRouteComponent, + title: 'Visible page' + }, + { + path: 'hidden-toolbar', + component: HiddenToolbarRouteComponent, + title: 'Hidden toolbar page', + data: { + showToolbar: false + } + } + ]) + ] + }).compileComponents() + + router = TestBed.inject(Router) + fixture = TestBed.createComponent(WireframeComponent) + component = fixture.componentInstance + }) + + it('should show the toolbar by default', async () => { + await router.navigateByUrl('/visible') + fixture.detectChanges() + + expect(component.showToolbar()).toBe(true) + expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeTruthy() + }) + + it('should hide the toolbar when the activated route data disables it', async () => { + await router.navigateByUrl('/hidden-toolbar') + fixture.detectChanges() + + expect(component.showToolbar()).toBe(false) + expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeFalsy() + }) +}) diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts index bee05b8b..f984532a 100644 --- a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts +++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts @@ -84,15 +84,11 @@ export class WireframeComponent { // Computed properties public showWireframe: Signal = computed(() => { - // This acts as a trigger for the computed property. - this.#navigationEnd() - - let child: ActivatedRouteSnapshot = this.#router.routerState.snapshot.root - while (child.firstChild) { - child = child.firstChild - } + return this.#getCurrentRouteSnapshot().data['showWireframe'] ?? true + }) - return child.data['showWireframe'] ?? true + public showToolbar: Signal = computed(() => { + return this.#getCurrentRouteSnapshot().data['showToolbar'] ?? true }) public sidenavMode: Signal<'over' | 'side'> = computed(() => { @@ -136,4 +132,16 @@ export class WireframeComponent { return this.#observer.isMatched(breakpoints) }) } + + #getCurrentRouteSnapshot(): ActivatedRouteSnapshot { + // This acts as a trigger for the computed properties that depend on the active route snapshot. + this.#navigationEnd() + + let child: ActivatedRouteSnapshot = this.#router.routerState.snapshot.root + while (child.firstChild) { + child = child.firstChild + } + + return child + } } From 5f2e331a48a667fbfebd70934eca408badd5d767 Mon Sep 17 00:00:00 2001 From: Glen Staes Date: Mon, 13 Apr 2026 19:59:23 +0200 Subject: [PATCH 2/3] Showcase route without toolbar in the global error handler page --- src/app/app.routes.ts | 5 ++++- .../global-error-handler.component.html | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 4f09af3e..34bb23f5 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -42,6 +42,9 @@ export const routes: Routes = [ { path: getRouteSegment(ROUTE_MAP.globalErrorHandler), component: GlobalErrorHandlerComponent, - title: 'navigation.global_error_handler' + title: 'navigation.global_error_handler', + data: { + showToolbar: false + } } ] diff --git a/src/app/global-error-handler/global-error-handler.component.html b/src/app/global-error-handler/global-error-handler.component.html index 5b5f1351..39211c0a 100644 --- a/src/app/global-error-handler/global-error-handler.component.html +++ b/src/app/global-error-handler/global-error-handler.component.html @@ -7,6 +7,12 @@ should never occur on a production environment, but it is useful for development and testing. " > +

Usage

Add the following snippet to your app module providers.


From 9b0fe46e3812ad3f97c34e05696b38dc747daa07 Mon Sep 17 00:00:00 2001
From: Glen Staes 
Date: Thu, 23 Apr 2026 17:44:14 +0200
Subject: [PATCH 3/3] Add global option to show toolbar

---
 .../src/lib/model/wireframe-options.ts        |  18 +++
 .../lib/wireframe/wireframe.component.html    |   2 +-
 .../lib/wireframe/wireframe.component.spec.ts | 120 +++++++++++++++---
 .../src/lib/wireframe/wireframe.component.ts  |   8 +-
 .../ppwcode/ng-wireframe/src/public-api.ts    |   1 +
 .../global-error-handler.component.html       |   2 +-
 src/main.ts                                   |   4 +-
 7 files changed, 133 insertions(+), 22 deletions(-)
 create mode 100644 projects/ppwcode/ng-wireframe/src/lib/model/wireframe-options.ts

diff --git a/projects/ppwcode/ng-wireframe/src/lib/model/wireframe-options.ts b/projects/ppwcode/ng-wireframe/src/lib/model/wireframe-options.ts
new file mode 100644
index 00000000..bf6759ab
--- /dev/null
+++ b/projects/ppwcode/ng-wireframe/src/lib/model/wireframe-options.ts
@@ -0,0 +1,18 @@
+import { InjectionToken, ValueProvider } from '@angular/core'
+
+export interface WireframeOptions {
+    showToolbar?: boolean
+}
+
+const defaultWireframeOptions: WireframeOptions = {
+    showToolbar: true
+}
+
+export const WIREFRAME_OPTIONS = new InjectionToken('WireframeOptions', {
+    factory: () => defaultWireframeOptions
+})
+
+export const provideWireframeOptions = (options?: WireframeOptions): ValueProvider => ({
+    provide: WIREFRAME_OPTIONS,
+    useValue: options ? options : defaultWireframeOptions
+})
diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html
index 121090ed..29bec895 100644
--- a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html
+++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html
@@ -56,7 +56,7 @@
                     
                 
             }
-            @if (showBreadcrumb() && showToolbar()) {
+            @if (resolvedShowBreadcrumb()) {
                 
                     
                         
diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts
index a5be060f..7753f21f 100644
--- a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts
+++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts
@@ -1,10 +1,13 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core'
 import { ComponentFixture, TestBed } from '@angular/core/testing'
-import { provideRouter, Router } from '@angular/router'
+import { provideRouter, Route, Router } from '@angular/router'
 import { provideTranslateService } from '@ngx-translate/core'
+import { provideBreadcrumbOptions } from '@ppwcode/ng-router'
+import { provideWireframeOptions, WireframeOptions } from '../model/wireframe-options'
 import { WireframeComponent } from './wireframe.component'
 
 @Component({
+    selector: 'ppw-visible-route',
     template: '',
     standalone: true,
     changeDetection: ChangeDetectionStrategy.OnPush
@@ -12,46 +15,88 @@ import { WireframeComponent } from './wireframe.component'
 class VisibleRouteComponent {}
 
 @Component({
+    selector: 'ppw-hidden-toolbar-route',
     template: '',
     standalone: true,
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 class HiddenToolbarRouteComponent {}
 
+@Component({
+    selector: 'ppw-toolbar-override-route',
+    template: '',
+    standalone: true,
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+class ToolbarOverrideRouteComponent {}
+
+@Component({
+    selector: 'ppw-hidden-breadcrumb-route',
+    template: '',
+    standalone: true,
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+class HiddenBreadcrumbRouteComponent {}
+
+const routes: Route[] = [
+    {
+        path: 'visible',
+        component: VisibleRouteComponent,
+        title: 'Visible page'
+    },
+    {
+        path: 'hidden-toolbar',
+        component: HiddenToolbarRouteComponent,
+        title: 'Hidden toolbar page',
+        data: {
+            showToolbar: false,
+            showBreadcrumb: true
+        }
+    },
+    {
+        path: 'toolbar-override',
+        component: ToolbarOverrideRouteComponent,
+        title: 'Toolbar override page',
+        data: {
+            showToolbar: true
+        }
+    },
+    {
+        path: 'hidden-breadcrumb',
+        component: HiddenBreadcrumbRouteComponent,
+        title: 'Hidden breadcrumb page',
+        data: {
+            showBreadcrumb: false
+        }
+    }
+]
+
 describe('WireframeComponent', () => {
     let component: WireframeComponent
     let fixture: ComponentFixture
     let router: Router
 
-    beforeEach(async () => {
+    const setup = async (wireframeOptions?: WireframeOptions): Promise => {
+        TestBed.resetTestingModule()
+
         await TestBed.configureTestingModule({
             imports: [WireframeComponent],
             providers: [
                 provideTranslateService({}),
-                provideRouter([
-                    {
-                        path: 'visible',
-                        component: VisibleRouteComponent,
-                        title: 'Visible page'
-                    },
-                    {
-                        path: 'hidden-toolbar',
-                        component: HiddenToolbarRouteComponent,
-                        title: 'Hidden toolbar page',
-                        data: {
-                            showToolbar: false
-                        }
-                    }
-                ])
+                provideBreadcrumbOptions(),
+                provideRouter(routes),
+                ...(wireframeOptions ? [provideWireframeOptions(wireframeOptions)] : [])
             ]
         }).compileComponents()
 
         router = TestBed.inject(Router)
         fixture = TestBed.createComponent(WireframeComponent)
         component = fixture.componentInstance
-    })
+        fixture.componentRef.setInput('showBreadcrumb', true)
+    }
 
     it('should show the toolbar by default', async () => {
+        await setup()
         await router.navigateByUrl('/visible')
         fixture.detectChanges()
 
@@ -60,10 +105,49 @@ describe('WireframeComponent', () => {
     })
 
     it('should hide the toolbar when the activated route data disables it', async () => {
+        await setup()
         await router.navigateByUrl('/hidden-toolbar')
         fixture.detectChanges()
 
         expect(component.showToolbar()).toBe(false)
         expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeFalsy()
     })
+
+    it('should hide the toolbar when wireframe options disable it by default', async () => {
+        await setup({ showToolbar: false })
+        await router.navigateByUrl('/visible')
+        fixture.detectChanges()
+
+        expect(component.showToolbar()).toBe(false)
+        expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeFalsy()
+    })
+
+    it('should show the toolbar when route data overrides the global default', async () => {
+        await setup({ showToolbar: false })
+        await router.navigateByUrl('/toolbar-override')
+        fixture.detectChanges()
+
+        expect(component.showToolbar()).toBe(true)
+        expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeTruthy()
+    })
+
+    it('should keep the breadcrumb visible when the toolbar is hidden by route data', async () => {
+        await setup()
+        await router.navigateByUrl('/hidden-toolbar')
+        fixture.detectChanges()
+
+        expect(component.showToolbar()).toBe(false)
+        expect(component.resolvedShowBreadcrumb()).toBe(true)
+        expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeFalsy()
+        expect(fixture.nativeElement.querySelector('ppw-breadcrumb')).toBeTruthy()
+    })
+
+    it('should let route data hide the breadcrumb even when the input enables it', async () => {
+        await setup()
+        await router.navigateByUrl('/hidden-breadcrumb')
+        fixture.detectChanges()
+
+        expect(component.resolvedShowBreadcrumb()).toBe(false)
+        expect(fixture.nativeElement.querySelector('ppw-breadcrumb')).toBeFalsy()
+    })
 })
diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts
index f984532a..c9c077d7 100644
--- a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts
+++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts
@@ -18,6 +18,7 @@ import { BreadcrumbComponent } from '@ppwcode/ng-router'
 import { filter } from 'rxjs'
 import { LeftSidenavComponent } from '../left-sidenav/left-sidenav.component'
 import { SidebarOptions } from '../model/sidebar-options'
+import { WIREFRAME_OPTIONS, WireframeOptions } from '../model/wireframe-options'
 import { NavigationItem } from '../navigation-item/navigation-item.model'
 import { ToolbarComponent } from '../toolbar/toolbar.component'
 
@@ -40,6 +41,7 @@ import { ToolbarComponent } from '../toolbar/toolbar.component'
 export class WireframeComponent {
     #observer: BreakpointObserver = inject(BreakpointObserver)
     #router: Router = inject(Router)
+    #wireframeOptions: WireframeOptions = inject(WIREFRAME_OPTIONS)
 
     #breakpointChange = toSignal(
         this.#observer.observe([Breakpoints.XSmall, Breakpoints.Small]).pipe(takeUntilDestroyed())
@@ -88,7 +90,11 @@ export class WireframeComponent {
     })
 
     public showToolbar: Signal = computed(() => {
-        return this.#getCurrentRouteSnapshot().data['showToolbar'] ?? true
+        return this.#getCurrentRouteSnapshot().data['showToolbar'] ?? this.#wireframeOptions.showToolbar ?? true
+    })
+
+    public resolvedShowBreadcrumb: Signal = computed(() => {
+        return this.showWireframe() && (this.#getCurrentRouteSnapshot().data['showBreadcrumb'] ?? this.showBreadcrumb())
     })
 
     public sidenavMode: Signal<'over' | 'side'> = computed(() => {
diff --git a/projects/ppwcode/ng-wireframe/src/public-api.ts b/projects/ppwcode/ng-wireframe/src/public-api.ts
index 6febb0da..5babd28c 100644
--- a/projects/ppwcode/ng-wireframe/src/public-api.ts
+++ b/projects/ppwcode/ng-wireframe/src/public-api.ts
@@ -7,3 +7,4 @@ export * from './lib/left-sidenav/left-sidenav.component'
 export * from './lib/toolbar/toolbar.component'
 export * from './lib/wireframe/wireframe.component'
 export * from './lib/model/sidebar-options'
+export * from './lib/model/wireframe-options'
diff --git a/src/app/global-error-handler/global-error-handler.component.html b/src/app/global-error-handler/global-error-handler.component.html
index 39211c0a..e8abde88 100644
--- a/src/app/global-error-handler/global-error-handler.component.html
+++ b/src/app/global-error-handler/global-error-handler.component.html
@@ -10,7 +10,7 @@
     
     

Usage

diff --git a/src/main.ts b/src/main.ts index f22c8fad..aa6f1b45 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,6 +16,7 @@ import { import { provideGlobalErrorHandler } from '@ppwcode/ng-common' import { PPW_TABLE_DEFAULT_OPTIONS } from '@ppwcode/ng-common-components' import { provideBreadcrumbOptions, providePaginationOptions, TranslatedPageTitleStrategy } from '@ppwcode/ng-router' +import { provideWireframeOptions } from '@ppwcode/ng-wireframe' import { AppComponent } from './app/app.component' import { routes } from './app/app.routes' import { EmptyAsyncResultComponent } from './app/table/empty-async-result.component' @@ -78,7 +79,8 @@ bootstrapApplication(AppComponent, { suffix: '.json' }) }), - provideBreadcrumbOptions() + provideBreadcrumbOptions(), + provideWireframeOptions() ] }).catch((err) => console.error(err))