Navbar
The <smart-navbar> component renders a top navigation bar with a logo, primary nav items, optional secondary row, search, action, notification and user-menu slots, plus a mobile menu toggle. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract NavbarBaseComponent defines the shared API — options (INavbarOptions), cssClass (alias class), mobileMenuOpen (two-way ModelSignal<boolean>), and the itemClick output. NavbarStandardComponent is a barebones placeholder using native <nav>, <a>, <button>, <ul> and <li> elements. NavbarComponent is the public wrapper that renders NavbarStandardComponent by default and accepts a custom replacement via NAVBAR_STANDARD_COMPONENT_TOKEN.
Usage
<smart-navbar [options]="options" (itemClick)="onItemClick($event)" />
Components
NavbarComponent (<smart-navbar>)
Main wrapper. Delegates to NavbarStandardComponent by default. When NAVBAR_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Re-emits itemClick and forwards two-way mobileMenuOpen.
NavbarStandardComponent (<smart-navbar-standard>)
Barebones placeholder using native HTML. Renders an outer wrapper with cssClass, a <nav class="navbar"> containing a mobile menu toggle button, optional logo (logoTpl or <img> from logoUrl/logoAlt, wrapped in <a> when logoHref provided), a primary <ul> of items (each rendered as <a class="item-link"> when href is provided, otherwise <button class="item-button"> emitting itemClick; gets current class when item.current === true), and template-ref slots searchTpl / actionTpl / notificationTpl / userMenuTpl. Optionally renders a <nav class="navbar-secondary"> row when options.secondaryItems is non-empty, and a .mobile-menu panel when mobileMenuOpen() is true.
NavbarPresetComponent (<smart-navbar-preset>)
Fully-styled, drop-in concrete implementation extending NavbarBaseComponent, translating the Preline collapsible navbar look to smart:-prefixed Tailwind classes with explicit dark: variants. Renders a <header> with a brand/logo slot (logoTpl, or <img> from logoUrl/logoAlt, wrapped in <a> when logoHref is set), a responsive primary link row (anchors for items with href, otherwise buttons emitting itemClick; current items get the active/aria-current="page" styling), the searchTpl / actionTpl / notificationTpl / userMenuTpl slots, an optional secondary link row from secondaryItems, and a mobile menu toggle. The mobile collapse is driven entirely by the two-way mobileMenuOpen signal (@if + (click)), so no Preline JS runtime is needed. options.dark switches to the solid dark color variant and options.menuButtonOnLeft moves the toggle ahead of the brand.
Register it via NAVBAR_STANDARD_COMPONENT_TOKEN to restyle every <smart-navbar>, or use the <smart-navbar-preset> selector directly. Because NavbarComponent projects inputs through NgComponentOutlet, the preset overrides cssClass as a plain input<string>('') (canonical name, no class alias) so external classes bind correctly.
NavbarBaseComponent (abstract)
Abstract base directive. Exposes:
options: InputSignal<INavbarOptions | undefined>cssClass: InputSignal<string>(aliasclass)mobileMenuOpen: ModelSignal<boolean>(defaultfalse)itemClick: OutputEmitterRef<INavbarItemClick>
INavbarItemClick = { itemId: string }.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<INavbarOptions | undefined> | - | Navbar configuration |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
mobileMenuOpen | ModelSignal<boolean> | false | Two-way bindable mobile menu open state |
Outputs
| Output | Type | Description |
|---|---|---|
itemClick | OutputEmitterRef<INavbarItemClick> | Emitted when a button-type nav item is clicked |
Extending the base class
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import {
INavbarOptions,
NAVBAR_STANDARD_COMPONENT_TOKEN,
NavbarBaseComponent,
NavbarComponent,
} from '@smartsoft001/angular';
/**
* A custom navbar built on `NavbarBaseComponent`.
*
* The base contributes the `options` and `class` inputs, the `mobileMenuOpen`
* model and the `itemClick` output; the implementation owns the markup and
* decides when to emit.
*/
@Component({
selector: 'docs-custom-navbar',
template: `
<nav [class]="containerClasses()">
@if (options()?.logoUrl) {
<a class="docs-navbar__logo" [href]="options()?.logoHref ?? '#'">
<img [src]="options()!.logoUrl" [alt]="options()?.logoAlt ?? ''" />
</a>
}
<ul class="docs-navbar__items">
@for (item of options()?.items ?? []; track item.id) {
<li>
<a
class="docs-navbar__item"
[href]="item.href ?? '#'"
[attr.aria-current]="item.current ? 'page' : null"
(click)="itemClick.emit({ itemId: item.id })"
>
{{ item.label }}
</a>
</li>
}
</ul>
<button
type="button"
class="docs-navbar__toggle"
aria-label="Toggle navigation"
[attr.aria-expanded]="mobileMenuOpen()"
(click)="mobileMenuOpen.set(!mobileMenuOpen())"
>
☰
</button>
@if (mobileMenuOpen()) {
<ul class="docs-navbar__mobile">
@for (item of options()?.items ?? []; track item.id) {
<li>
<a [href]="item.href ?? '#'">{{ item.label }}</a>
</li>
}
</ul>
}
</nav>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomNavbarComponent extends NavbarBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
containerClasses = computed(() => {
const classes = ['docs-navbar'];
if (this.options()?.dark) classes.push('docs-navbar--dark');
if (this.options()?.menuButtonOnLeft) {
classes.push('docs-navbar--menu-left');
}
const extra = this.cssClass();
if (extra) classes.push(extra);
return classes.join(' ');
});
}
/**
* Registering the implementation against `NAVBAR_STANDARD_COMPONENT_TOKEN`
* makes every `<smart-navbar>` in this injector render it instead of the
* standard variation.
*
* NgComponentOutlet forwards `mobileMenuOpen` as a plain input, so the model
* the custom navbar writes to stays local; the wrapper's `(itemClick)` and
* `(mobileMenuOpenChange)` never fire.
*/
@Component({
selector: 'docs-navbar-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NavbarComponent],
providers: [
{
provide: NAVBAR_STANDARD_COMPONENT_TOKEN,
useValue: CustomNavbarComponent,
},
],
template: `<smart-navbar [options]="options" />`,
})
export class NavbarCustomExampleComponent {
options: INavbarOptions = {
layout: 'simple',
dark: false,
logoUrl: 'https://avatars.githubusercontent.com/u/10416742?s=200&v=4',
logoAlt: 'Brand',
logoHref: '#',
items: [
{ id: 'landing', label: 'Landing', href: '#', current: true },
{ id: 'account', label: 'Account', href: '#' },
{ id: 'work', label: 'Work', href: '#' },
{ id: 'blog', label: 'Blog', href: '#' },
],
};
}
Source
The component lives in packages/shared/angular/src/lib/components/navbar and is documented for Claude Code by the angular-components-navbar skill.