Skip to main content
cngx-src documentation

CngxTabsRouteSync

DirectivePrimaryv0.1.0WCAG AA

projects/common/tabs/route-sync.directive.ts

Import#

import { CngxTabsRouteSync } from '@cngx/common/tabs'

Description#

Router-outlet integration for tab groups.
Opt-in via [cngxTabsRouteSync] on the presenter element. Each tab is an Angular child route whose component renders into a <router-outlet>; switching tabs navigates, and a CanDeactivate guard can gate the leave.

Supplies the presenter's commit-action through the CNGX_TABS_COMMIT_ACTION DI fallback (no consumer [commitAction] hand-binding) and pins pessimistic mode so the active tab follows the resolved route - a guard-cancelled switch keeps the old tab. The routing gate reuses the presenter's commit lifecycle verbatim; the router navigation is the async op.

Seeds the active tab from the current URL on mount and reflects external navigations (back/forward, direct URL) into activeIndex without re-navigating - those writes bypass the commit-action, since the router is already at the resolved route.

Router is optional - without it the directive logs a single dev warning via afterNextRender, exposes a null action, and becomes a no-op. Mirrors CngxTabsFragmentSync; every activeIndex write inside an effect sits in untracked().

The mental model. It turns the tab group into the router's view switcher: the tabs choose which child route renders in the <router-outlet>, not which inline template shows.
Concretely, a Settings page with Profile / Security / Billing tabs then gets, for free: each tab its own URL (/settings/profile) so a refresh or a shared link stays on the tab; browser back/forward between tabs; Billing lazy-loading its chunk only when first opened; and Profile blocking the switch via a CanDeactivate guard while its form is dirty - all while keeping the role="tab" keyboard and ARIA model.

Use it when each tab's content is a real Angular route - a lazy chunk, a resolver, or a CanActivate / CanDeactivate guard - rendered into a <router-outlet>, and you want browser back/forward and deep links to move between tabs while the route's own guard gates the switch (e.g. an unsaved-changes prompt on leave).
If the tabs are purely visual, use a plain <cngx-tab-group> with no sync directive; if you only want the active tab reflected in the URL while content stays inline, use CngxTabsFragmentSync.

What this is not.
Not a per-tab routerLink:
the tabs stay role="tab" buttons and the directive drives router.navigate centrally through the commit lifecycle, so there are no anchors, no href, and no native open-in-new-tab / middle-click / copy-link.
For real link semantics (right-click, ctrl-click) reach for the CNGX <cngx-tab-nav> and <a cngx-tab-link routerLink> variant instead.
Not URL deep-linking either - that is CngxTabsFragmentSync (a #tab= fragment or ?tab= query-param); this directive renders child route components into a <router-outlet>.
And not optimistic: the active tab moves only once the route resolves, so a CanDeactivate guard can cancel the switch.

// Each tab is a child route; the editor guards its own exit.
const ROUTES: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'editor' },
  { path: 'editor', component: EditorPage, canDeactivate: [unsavedChangesGuard] },
  { path: 'preview', component: PreviewPage },
  { path: 'settings', component: SettingsPage },
];
<!-- Tab ids match the child segments (default routeFor = (h) => [h.id]).
     Switching a tab navigates; the guard gates leaving the editor;
     back/forward and /account/settings deep links move the active tab. -->
<cngx-tab-group cngxTabsRouteSync aria-label="Account">
  <div cngxTab id="editor" label="Editor"></div>
  <div cngxTab id="preview" label="Preview"></div>
  <div cngxTab id="settings" label="Settings"></div>
</cngx-tab-group>

<!-- The active tab's routed component renders here. -->
<router-outlet></router-outlet>

Metadata#

Providers#

CNGX_TABS_COMMIT_ACTION
useExisting CngxTabsRouteSync

Relationships

Index#

Properties

Inputs

Derived State

Inputs#

Maps a tab handle to its router command array. Default (handle) => [handle.id] - the tab id is the child segment. Override for real route paths.

default cngxDefaultTabRoute

Instance Properties#

signal()Readonly

Pessimistic-only - not exposed as an input. The route-sync owns the mode so optimistic (show-then-revert) can never flash the target tab before a CanDeactivate guard decides.

signal('pessimistic')