Extract NavItem to shared domain library
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:
