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

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.

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.

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.

Abstract base directive. Exposes:

  • options: InputSignal<INavbarOptions | undefined>
  • cssClass: InputSignal<string> (alias class)
  • mobileMenuOpen: ModelSignal<boolean> (default false)
  • itemClick: OutputEmitterRef<INavbarItemClick>

INavbarItemClick = { itemId: string }.

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<INavbarOptions | undefined>-Navbar configuration
classInputSignal<string>''External CSS classes (alias for cssClass)
mobileMenuOpenModelSignal<boolean>falseTwo-way bindable mobile menu open state

Outputs

OutputTypeDescription
itemClickOutputEmitterRef<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())"
      >
        &#9776;
      </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.