Page
The <smart-page> component provides a flexible page-header/layout wrapper with a map-based variant dispatch mechanism. It renders a default PageStandardComponent which can be extended with additional variants via PAGE_VARIANT_COMPONENTS_TOKEN. Projection is driven by TemplateRef slots declared on IPageOptions, plus a default <ng-content> fallback for the body.
Usage
<smart-page [options]="options">
<p>12 people have access to this workspace.</p>
</smart-page>
Components
PageComponent (<smart-page>)
Main wrapper component. Always renders via NgComponentOutlet. Looks up the target variant component from a merged map of baseMap + the optional PAGE_VARIANT_COMPONENTS_TOKEN map, keyed by options.variant (default 'standard'). Falls back to PageStandardComponent when the variant is unknown. Captures <ng-content> into a TemplateRef and passes it to the target component as options.bodyTpl unless the caller provided an explicit bodyTpl.
PageStandardComponent (<smart-page-standard>)
Default concrete implementation and the value for baseMap['standard']. Renders a Tailwind-styled header with title, optional back button, optional search input, endButtons via <smart-button>, and a body section driven by options.bodyTpl.
PageBaseComponent (abstract)
Abstract base directive for extending custom page variants. Exposes options, cssClass, back(), isMobile, and a contentTpl view child for variants that need to re-project the main content.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<IPageOptions|null> | - | Page configuration (see IPageOptions) |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
SmartPageVariant
Variant identifier used to select a concrete page component from the merged variant map. The intersection with (string & {}) keeps 'standard' as a suggested literal while allowing arbitrary custom keys.
IPageOptions
| Field | Type | Description |
|---|---|---|
title | string | Required. Page title (translated). |
hideHeader | boolean | Hide the entire header block. |
hideMenuButton | boolean | Reserved for layouts with a menu button. |
showBackButton | boolean | Render a back arrow that calls Location.back(). |
endButtons | Array<IIconButtonOptions> | Action buttons rendered via <smart-button>. |
search | { text: Signal<string>; set: ... } | Inline search input. |
variant | SmartPageVariant | Selects which variant component to render. Defaults to 'standard'. |
bodyTpl | TemplateRef<unknown> | Explicit body template. Falls back to projected <ng-content> when absent. |
breadcrumbsTpl | TemplateRef<unknown> | Breadcrumbs slot. |
metaTpl | TemplateRef<unknown> | Meta slot (status, timestamps, etc.). |
avatarTpl | TemplateRef<unknown> | Avatar slot. |
bannerTpl | TemplateRef<unknown> | Banner slot. |
filtersTpl | TemplateRef<unknown> | Filters slot. |
logoTpl | TemplateRef<unknown> | Logo slot. |
statsTpl | TemplateRef<unknown> | Stats slot. |
subtitleTpl | TemplateRef<unknown> | Subtitle slot. |
navTpl | TemplateRef<unknown> | Navigation slot. |
sidebarTpl | TemplateRef<unknown> | Sidebar slot. |
IIconButtonOptions (relevant fields)
| Field | Type | Description |
|---|---|---|
icon | string | Icon name (tracked by the @for) |
text | string | Button label (translated) |
number | number | Optional badge counter |
handler | () => void | Click handler |
disabled$ | Observable<boolean> | Optional disabled stream |
PAGE_VARIANT_COMPONENTS_TOKEN
InjectionToken of type Partial<Record<SmartPageVariant, Type<PageBaseComponent>>>. Provide a map to register additional variants. The map is merged on top of baseMap (which contains the default { standard: PageStandardComponent }), so you can both add new variants and override 'standard'.
Extending the base class
import { NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
TemplateRef,
Type,
viewChild,
ViewEncapsulation,
} from '@angular/core';
import {
IPageOptions,
PAGE_VARIANT_COMPONENTS_TOKEN,
PageBaseComponent,
PageComponent,
SmartPageVariant,
} from '@smartsoft001/angular';
/**
* A custom page shell built on `PageBaseComponent`.
*
* The base contributes the `options` and `class` inputs, the `back()` helper
* that delegates to `Location`, and the `appService` / `hardwareService`
* injections; the implementation owns the chrome around the body.
*/
@Component({
selector: 'docs-custom-page',
template: `
<div [class]="containerClasses()">
@if (!options()?.hideHeader) {
@if (options()?.breadcrumbsTpl) {
<nav class="docs-page__breadcrumbs" aria-label="Breadcrumb">
<ng-container [ngTemplateOutlet]="options()!.breadcrumbsTpl!" />
</nav>
}
<header class="docs-page__header">
@if (options()?.showBackButton) {
<button
type="button"
class="docs-page__back"
aria-label="Go back"
(click)="back()"
>
←
</button>
}
<h1 class="docs-page__title">{{ options()?.title }}</h1>
</header>
}
<!--
Unlike the other wrappers, smart-page wraps its own ng-content in a
TemplateRef and passes it down as options.bodyTpl, so projected content
survives the NgComponentOutlet hop.
-->
<section class="docs-page__body">
@if (options()?.bodyTpl; as bodyTpl) {
<ng-container [ngTemplateOutlet]="bodyTpl" />
}
</section>
</div>
`,
imports: [NgTemplateOutlet],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomPageComponent extends PageBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
containerClasses = computed(() =>
['docs-page', this.isMobile ? 'docs-page--mobile' : '', this.cssClass()]
.filter(Boolean)
.join(' '),
);
}
/**
* The page wrapper resolves its shell from a map of variants rather than from
* a single standard-component token, so the implementation is registered under
* the variant name that `IPageOptions.variant` asks for. The map is merged
* over the built-in one, so `'standard'` keeps working for everything else.
*/
const DOCS_PAGE_VARIANT_COMPONENTS: Partial<
Record<SmartPageVariant, Type<PageBaseComponent>>
> = { docs: CustomPageComponent };
@Component({
selector: 'docs-page-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [PageComponent],
providers: [
{
provide: PAGE_VARIANT_COMPONENTS_TOKEN,
useValue: DOCS_PAGE_VARIANT_COMPONENTS,
},
],
template: `
<smart-page [options]="options()">
<p>Account settings and permissions</p>
</smart-page>
<ng-template #breadcrumbsTpl>
<a href="#">Users</a>
<span aria-hidden="true">/</span>
<span>Alice Johnson</span>
</ng-template>
`,
})
export class PageCustomExampleComponent {
breadcrumbsTpl = viewChild<TemplateRef<unknown>>('breadcrumbsTpl');
options = computed<IPageOptions>(() => ({
title: 'Alice Johnson',
variant: 'docs',
showBackButton: true,
breadcrumbsTpl: this.breadcrumbsTpl(),
}));
}
Preset
PagePresetComponent (<smart-page-preset>) is a styled 'preset' variant that extends PageStandardComponent. It renders a full application-shell layout instead of the plain standard header: a bordered <header>, a gray page body and a content card. Every utility is smart:-prefixed with explicit smart:dark:* twins.
Registration
The preset is dispatched by map key 'preset' — the built-in 'standard' variant is untouched. Register the ready-made map for PAGE_VARIANT_COMPONENTS_TOKEN:
Then set variant: 'preset' on IPageOptions:
Zones (all keyed by data-role)
data-role | Source | Notes |
|---|---|---|
page | outer wrapper | bg-gray-50 dark:bg-gray-900; picks up cssClass. |
banner | bannerTpl | Full-width strip above the header. |
header | header shell | Skipped entirely when hideHeader is true. |
breadcrumbs | breadcrumbsTpl | Row at the top of the header container. |
title | title (h1) + subtitleTpl | Title row also holds back button, avatar/logo, actions. |
actions | search + endButtons | Right side of the title row; hidden when both are absent. |
meta | metaTpl + statsTpl | Row under the title; hidden when both are absent. |
filters | filtersTpl | Bar under the header. |
sidebar | sidebarTpl | Rendered as <aside> in a flex row (lg: and up). |
body | bodyTpl / <ng-content> | Rendered inside a bordered content card. |
The back button (button[data-role="back"]) appears only when showBackButton is true and calls the inherited back(). A hamburger button[data-role="menu-button"] renders unless hideMenuButton is true.
cssClass
Registered through the token, the wrapper forwards inputs canonically via NgComponentOutlet ({ options, cssClass }), so the preset does override cssClass = input<string>('') (drops the class alias). External classes land on the data-role="page" wrapper.
Documented gaps
hideMenuButtononly toggles a decorative hamburger button; there is no menu panel wired to it.- Unused slots (
bannerTpl,breadcrumbsTpl,metaTpl,statsTpl,filtersTpl,sidebarTpl,avatarTpl,logoTpl,subtitleTpl) simply render nothing when omitted;navTplis not consumed by this preset.
Source
The component lives in packages/shared/angular/src/lib/components/page and is documented for Claude Code by the angular-components-page skill.