Skip to main content

Extract NavItem to shared domain library

September 30, 2025About 1 min

Extract NavItem to shared domain library

Splitting the navbar into presentational and smart components was the right move. One thing still needs attention: the NavItem interface. It’s currently declared inside the UI library (libs/swe-demo/ui) where the presentational Navbar lives, even though the data is constructed and passed by the smart NavbarContainer via @Input() items: NavItem[].

Keeping a domain model inside a UI package couples consumers to Angular and limits reuse. Instead, move NavItem to a plain, framework-free shared/domain library (e.g., libs/shared/domain) and import it where needed. This creates a single source of truth, keeps the UI layer purely presentational, and allows multiple libraries to share the type without duplication.

Step 1 - Add the NavItem interface to our shared domain lib

Add nav-item.ts to the lib folder inside shared/domain/src. You can decide to create a parent folder models to keep the folder somewhat organised:

// libs/shared/domain/src/lib/models/nav-item.ts
export interface NavItem {
  label: string;
  path: string;
  icon?: string; // optional material icon name
}

Export it in the barrel file (index.ts). (You're allowed to remove the shared-domain component):

export * from "./lib/models/nav-item";

Step 2 - Update the presentational Navbar to use the domain type

Remove the inline NavItem interface in the UI component and import it from the domain lib.

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { RouterModule } from '@angular/router';
import { NavItem } from '@swe-monorepo/shared-domain';

@Component({
  selector: 'lib-swe-demo-ui-navbar',
  imports: [RouterModule],
  templateUrl: './navbar.html',
  styleUrl: './navbar.css',
})
export class Navbar {
  @Input() items: NavItem[] = [];
  @Input() showUser = false;
  @Output() navigate = new EventEmitter<string>();
  @Output() logout = new EventEmitter<void>();
}


 













Step 3 - Update the smart NavbarContainer to use the domain type

import { Component, computed, inject } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import { Navbar } from '@swe-monorepo/swe-demo-ui';
import { NavItem } from '@swe-monorepo/shared-domain';

@Component({
  selector: 'lib-swe-demo-feature-navbar-container',
  imports: [Navbar, RouterModule],
  templateUrl: './navbar-container.html',
  styleUrl: './navbar-container.css',
})
export class NavbarContainer {
  private router = inject(Router);

  readonly items = computed<NavItem[]>(() => ([
    { label: 'Home', path: '/' },
    { label: 'Products', path: '/products' },
    { label: 'Account', path: '/account' },
  ]));



 










 




Step 4: Verify

Verify if the navbar still works correctly by serving the swe-demo application. Also check the ng graph and note the extra dependency:

graph after refactor
graph after refactor