Add New App

Documentation for Developers

How to Add a New App to the Monorepo

This guide documents the complete process of adding a new Angular application to our NX monorepo, based on the successful creation of the space-map application.

📅 Last Updated: October 23, 2025
✨ Recent Updates:

  • Updated StorageService API (no asSignal(), use set/get methods with manual signals)
  • Updated theme-system library names and import paths
  • Corrected provider names and imports
  • Added guidance on building library dependencies first
  • Updated SCSS import paths for theme system
  • Added troubleshooting section for common app issues
  • Clarified @libs-foundational/core-models usage (not @core-types)

Prerequisites

Before creating a new app, ensure you have:

Step 1: Generate the Application

Use the NX Angular application generator:

npx nx g @nx/angular:application my-app-name

Configuration Options

When prompted, use these settings for consistency:

Step 2: Configure TypeScript Paths

Add your new app to the TypeScript path mapping in tsconfig.base.json:

{
  "compilerOptions": {
    "paths": {
      // ... existing paths ...
      "@my-app-name/*": ["apps/my-app-name/src/*"]
    }
  }
}

Step 3: Install and Configure Core Dependencies

Add Core Libraries

Add essential dependencies to your app's project.json:

{
  "targets": {
    "build": {
      // ... other config
      "options": {
        "tsConfig": "apps/my-app-name/tsconfig.app.json",
        "buildOptimizer": false
      }
    }
  }
}

Update Dependencies in Package Dependencies

Ensure your app declares dependencies on core libraries:

// In your app's main dependencies (implicitly through imports)
import { UxMultiSelectComponent } from 'libs/ux-ui-extended/src/lib/components';
import { SafeAny } from '@libs-foundational/core-models'; 
import { ThemeApplicationService } from 'libs/theme-system/theme-runtime/src/lib';

Step 4: Integrate Core Systems (ESSENTIAL) ⭐

Before any application logic, integrate all three core systems:

Configure Complete System Integration in main.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { importProvidersFrom, APP_INITIALIZER } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

// ✅ Core Systems Integration
import { provideFlashPersistenceWithNGXS } from '@flash-persistence';
import { provideIntelligentStorage } from '@storage-system';
import { provideThemeSystem } from '@theme-system';

import { AppComponent } from './app/app.component';
import { appRoutes } from './app/app.routes';
import { APP_STATES } from './app/state';
import { environment } from '../environments/environment';

bootstrapApplication(AppComponent, {
  providers: [
    // 🧠 Flash-Persistence with NGXS Compatibility
    provideFlashPersistenceWithNGXS({
      states: APP_STATES,
      namespace: 'my-app',
      devMode: !environment.production
    }),

    // 🗄️ Intelligent Storage System (auto-configured)
    provideIntelligentStorage(),

    // 🎨 Theme System Integration
    provideThemeSystem(),

    // ⚡ INSTANT Theme Loading (Optional but Recommended)
    {
      provide: APP_INITIALIZER,
      useFactory: () => {
        return () => {
          if (typeof window === 'undefined') return Promise.resolve();
          
          console.log('🚀 INSTANT THEME LOADER: Starting immediate theme application...');
          
          const activeThemeId = localStorage.getItem('theme-system:active-theme-id');
          if (activeThemeId) {
            document.documentElement.classList.add(`theme-${activeThemeId}`);
            document.documentElement.setAttribute('data-theme-id', activeThemeId);
            document.documentElement.setAttribute('data-theme-mode',
              activeThemeId.includes('dark') ? 'dark' : 'light');
          }
          
          return Promise.resolve();
        };
      },
      multi: true
    },

    // Standard Angular Providers
    importProvidersFrom(
      CommonModule,
      FormsModule,
      RouterModule.forRoot(appRoutes, {
        initialNavigation: 'enabledBlocking'
      })
    )
  ]
}).catch(err => console.error(err));

Create App State Structure

// apps/my-app-name/src/app/state/index.ts
import { AppState } from './app.state';
import { UserState } from './user.state';
import { FeatureState } from './feature.state';

export const APP_STATES = [
  AppState,
  UserState,
  FeatureState
];

export * from './app.state';
export * from './user.state';
export * from './feature.state';

Create Main App State

// apps/my-app-name/src/app/state/app.state.ts
import { State, Action, Selector } from '@flash-persistence/ngxs-compat';

export interface AppStateModel {
  isInitialized: boolean;
  theme: 'light' | 'dark' | 'auto';
  sidebarOpen: boolean;
  notifications: {
    enabled: boolean;
    count: number;
  };
}

@State<AppStateModel>({
  name: 'app',
  defaults: {
    isInitialized: false,
    theme: 'auto',
    sidebarOpen: false,
    notifications: {
      enabled: true,
      count: 0
    }
  }
})
export class AppState {
  @Selector()
  static isReady(state: AppStateModel): boolean {
    return state.isInitialized;
  }

  @Selector()
  static theme(state: AppStateModel): string {
    return state.theme;
  }

  @Selector()
  static sidebarOpen(state: AppStateModel): boolean {
    return state.sidebarOpen;
  }

  @Action({ type: 'Initialize' })
  initialize(ctx: any) {
    ctx.setState({ ...ctx.getState(), isInitialized: true });
  }

  @Action({ type: 'SetTheme' })
  setTheme(ctx: any, theme: string) {
    ctx.setState({ ...ctx.getState(), theme });
  }

  @Action({ type: 'ToggleSidebar' })
  toggleSidebar(ctx: any) {
    const state = ctx.getState();
    ctx.setState({ ...state, sidebarOpen: !state.sidebarOpen });
  }
}

Step 5: Setup Modern Application Structure

Create Modern App Component with Full Theme Integration [[memory:6454111]]

// apps/my-app-name/src/app/app.component.ts
import { Component, OnInit, inject, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { Store } from '@flash-persistence';
import { StorageService } from '@storage-system';
import { ThemeManagerService } from '@theme-system';
import { AppState } from './state/app.state';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterModule],
  template: `
    <div 
      class="app-container ux-min-h-screen ux-bg-background ux-text-on-background ux-flex ux-flex-col" 
      [class.sidebar-open]="sidebarOpen()"
      [attr.data-theme]="currentTheme()"
    >
      @if (isAppReady()) {
        <!-- App Header -->
        <header class="app-header ux-bg-primary ux-text-on-primary ux-shadow-lg ux-z-50">
          <div class="header-content ux-flex ux-items-center ux-justify-between ux-p-4">
            <div class="ux-flex ux-items-center ux-space-x-4">
              <button 
                type="button"
                class="sidebar-toggle ux-btn ux-btn-ghost ux-p-2 ux-rounded ux-text-on-primary ux-hover:ux-bg-primary-hover"
                (click)="toggleSidebar()"
                [attr.aria-label]="sidebarOpen() ? 'Close sidebar' : 'Open sidebar'"
              >
                <i [class]="sidebarOpen() ? 'fa fa-times' : 'fa fa-bars'" aria-hidden="true"></i>
              </button>
              
              <h1 class="app-title ux-text-xl ux-font-bold ux-text-on-primary">{{ appTitle() }}</h1>
            </div>
            
            <div class="header-actions ux-flex ux-items-center ux-space-x-2">
              <button 
                type="button"
                class="theme-toggle ux-btn ux-btn-ghost ux-p-2 ux-rounded ux-text-on-primary ux-hover:ux-bg-primary-hover"
                (click)="toggleTheme()"
                [attr.aria-label]="'Switch to ' + nextTheme() + ' theme'"
              >
                <i [class]="themeIcon()" aria-hidden="true"></i>
              </button>
              
              @if (hasNotifications()) {
                <button 
                  type="button" 
                  class="notifications-button ux-btn ux-btn-ghost ux-p-2 ux-rounded ux-relative ux-text-on-primary ux-hover:ux-bg-primary-hover"
                >
                  <i class="fa fa-bell" aria-hidden="true"></i>
                  <span class="notification-count ux-absolute ux-top-0 ux-right-0 ux-bg-accent ux-text-on-accent ux-text-xs ux-rounded-full ux-px-1 ux-min-w-5 ux-h-5 ux-flex ux-items-center ux-justify-center">
                    {{ notificationCount() }}
                  </span>
                </button>
              }
            </div>
          </div>
        </header>

        <div class="app-body ux-flex ux-flex-1 ux-relative">
          <!-- Sidebar -->
          @if (sidebarOpen()) {
            <aside class="app-sidebar ux-bg-surface ux-text-on-surface ux-w-64 ux-shadow-lg ux-z-40">
              <nav class="sidebar-nav ux-p-4">
                <div class="nav-section ux-mb-6">
                  <h3 class="ux-text-sm ux-font-semibold ux-text-on-surface-variant ux-uppercase ux-tracking-wide ux-mb-3">Main</h3>
                  <a 
                    routerLink="/dashboard" 
                    class="nav-item ux-flex ux-items-center ux-space-x-3 ux-p-3 ux-rounded ux-text-on-surface ux-hover:ux-bg-surface-hover ux-transition"
                  >
                    <i class="fa fa-dashboard ux-text-primary" aria-hidden="true"></i>
                    <span>Dashboard</span>
                  </a>
                </div>
              </nav>
            </aside>
          }

          <!-- Main Content -->
          <main class="app-main ux-flex-1 ux-p-6 ux-overflow-auto">
      <router-outlet></router-outlet>
          </main>
        </div>

        <!-- Loading Overlay for app initialization -->
        @if (isLoading()) {
          <div class="loading-overlay ux-fixed ux-inset-0 ux-bg-background ux-bg-opacity-80 ux-flex ux-items-center ux-justify-center ux-z-50">
            <div class="loading-content ux-text-center ux-space-y-4">
              <i class="fa fa-spinner fa-spin fa-2x ux-text-primary" aria-hidden="true"></i>
              <p class="ux-text-on-background">Initializing application...</p>
            </div>
          </div>
        }
      } @else {
        <!-- App loading state -->
        <div class="app-loading ux-min-h-screen ux-flex ux-items-center ux-justify-center ux-bg-background">
          <div class="loading-spinner ux-text-center ux-space-y-4">
            <i class="fa fa-cog fa-spin fa-3x ux-text-primary" aria-hidden="true"></i>
            <p class="ux-text-on-background">Loading application...</p>
          </div>
          <h2>Loading My App</h2>
          <p>Preparing your workspace...</p>
        </div>
      }
    </div>
  `,
  styles: [`
    @use 'index' as theme;
    
    :host {
      display: block;
      height: 100vh;
      overflow: hidden;
    }
    
    .app-container {
      display: grid;
      grid-template-areas: 
        "header header"
        "main main";
      grid-template-rows: auto 1fr;
      grid-template-columns: 1fr;
      height: 100vh;
      background: var(--ux-background);
      transition: all 0.3s ease;
      
      &.sidebar-open {
        grid-template-areas: 
          "header header"
          "sidebar main";
        grid-template-columns: 280px 1fr;
      }
      
      .app-header {
        grid-area: header;
        @include theme.ux-surface-elevation(2);
        background: var(--ux-surface);
        border-bottom: 1px solid var(--ux-outline-variant);
        z-index: 100;
        
        .header-content {
          display: flex;
          align-items: center;
          justify-content: space-between;
          padding: var(--ux-spacing-md) var(--ux-spacing-lg);
          
          .sidebar-toggle {
            @include theme.ux-button('ghost');
            padding: var(--ux-spacing-sm);
          }
          
          .app-title {
            @include theme.ux-typography('heading', 'lg');
            margin: 0;
            color: var(--ux-on-surface);
            flex: 1;
            text-align: center;
          }
          
          .header-actions {
            display: flex;
            gap: var(--ux-spacing-sm);
            
            .theme-toggle {
              @include theme.ux-button('ghost');
            }
            
            .notifications-button {
              @include theme.ux-button('ghost');
              position: relative;
              
              .notification-count {
                position: absolute;
                top: -4px;
                right: -4px;
                background: var(--ux-error);
                color: var(--ux-on-error);
                border-radius: 50%;
                min-width: 18px;
                height: 18px;
                font-size: 0.75rem;
                display: flex;
                align-items: center;
                justify-content: center;
              }
            }
          }
        }
      }
      
      .app-sidebar {
        grid-area: sidebar;
        @include theme.ux-surface-elevation(1);
        background: var(--ux-surface-container);
        border-right: 1px solid var(--ux-outline-variant);
        overflow-y: auto;
        
        .sidebar-nav {
          padding: var(--ux-spacing-md);
          
          .nav-section {
            margin-bottom: var(--ux-spacing-lg);
            
            h3 {
              @include theme.ux-typography('title', 'sm');
              color: var(--ux-on-surface-variant);
              margin-bottom: var(--ux-spacing-sm);
              text-transform: uppercase;
              font-size: 0.8rem;
              letter-spacing: 0.5px;
            }
            
            .nav-item {
              @include theme.ux-button('ghost');
              width: 100%;
              justify-content: flex-start;
              gap: var(--ux-spacing-sm);
              margin-bottom: var(--ux-spacing-xs);
              text-decoration: none;
            }
          }
        }
      }
      
      .app-main {
        grid-area: main;
        overflow-y: auto;
        padding: var(--ux-spacing-lg);
        background: var(--ux-background);
      }
      
      .loading-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 9999;
        
        .loading-content {
          @include theme.ux-card(true);
          padding: var(--ux-spacing-xl);
          text-align: center;
          
          i {
            color: var(--ux-primary);
            margin-bottom: var(--ux-spacing-md);
          }
          
          p {
            color: var(--ux-on-surface-variant);
            margin: 0;
          }
        }
      }
      
      .app-loading {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        height: 100vh;
        text-align: center;
        
        .loading-spinner {
          margin-bottom: var(--ux-spacing-lg);
          
          i {
            color: var(--ux-primary);
          }
        }
        
        h2 {
          @include theme.ux-typography('heading', 'xl');
          color: var(--ux-on-background);
          margin-bottom: var(--ux-spacing-sm);
        }
        
        p {
          color: var(--ux-on-background-variant);
        }
      }
    }
  `]
})
export class AppComponent implements OnInit {
  private readonly store = inject(Store);
  private readonly storage = inject(StorageService);

  // App state signals from Flash Persistence
  readonly isAppReady = this.store.select(AppState.isReady);
  readonly currentTheme = this.store.select(AppState.theme);
  readonly sidebarOpen = this.store.select(AppState.sidebarOpen);

  // Storage-backed user preferences (manually managed signal)
  private readonly _userPreferences = signal<{
    appTitle?: string;
    enableNotifications?: boolean;
    notificationCount?: number;
  }>({
    appTitle: 'My App',
    enableNotifications: true,
    notificationCount: 0
  });
  readonly userPreferences = computed(() => this._userPreferences());

  // Computed properties
  readonly appTitle = computed(() => this.userPreferences().appTitle || 'My App');
  readonly hasNotifications = computed(() => 
    this.userPreferences().enableNotifications && (this.userPreferences().notificationCount || 0) > 0
  );
  readonly notificationCount = computed(() => this.userPreferences().notificationCount || 0);
  readonly isLoading = signal(false);

  readonly themeIcon = computed(() => {
    const theme = this.currentTheme();
    switch (theme) {
      case 'light': return 'fa fa-sun';
      case 'dark': return 'fa fa-moon';
      default: return 'fa fa-adjust';
    }
  });

  readonly nextTheme = computed(() => {
    const theme = this.currentTheme();
    switch (theme) {
      case 'light': return 'dark';
      case 'dark': return 'auto';
      default: return 'light';
    }
  });

  async ngOnInit(): Promise<void> {
    this.isLoading.set(true);
    
    try {
      // Initialize app state
      this.store.dispatch({ type: 'Initialize' });
      
      // Load user preferences from intelligent storage
      await this.loadUserPreferences();
      
      console.log('✅ App initialized successfully');
    } catch (error) {
      console.error('❌ App initialization failed:', error);
    } finally {
      this.isLoading.set(false);
    }
  }

  toggleSidebar(): void {
    this.store.dispatch({ type: 'ToggleSidebar' });
  }

  async toggleTheme(): Promise<void> {
    const nextTheme = this.nextTheme();
    this.store.dispatch({ type: 'SetTheme', payload: nextTheme });
    
    // Persist theme preference using correct storage API
    await this.storage.set('app:theme-preference', nextTheme);
  }

  private async loadUserPreferences(): Promise<void> {
    try {
      // Load theme preference
      const savedTheme = await this.storage.get<string>('app:theme-preference');
      if (savedTheme) {
        this.store.dispatch({ type: 'SetTheme', payload: savedTheme });
      }

      // Load user preferences and update signal
      const savedPrefs = await this.storage.get<{
        appTitle?: string;
        enableNotifications?: boolean;
        notificationCount?: number;
      }>('app:user-preferences');
      
      if (savedPrefs) {
        this._userPreferences.set(savedPrefs);
      }
    } catch (error) {
      console.error('Failed to load user preferences:', error);
    }
  }
}

Step 5: Configure Styling and Theme Integration

Setup Theme System Integration

Create a theme configuration file src/app/config/theme.config.ts:

import { ThemeApplicationService } from 'libs/theme-system/theme-runtime/src/lib';

export const setupTheme = (themeService: ThemeApplicationService) => {
  // Configure your app-specific theme settings
  themeService.applyTheme({
    primary: '#7a68f3',
    secondary: '#23a6d5',
    // ... other theme colors
  });
};

Configure Global Styles

Create src/styles.scss with Proper Theme Integration:

// ✅ CORRECT: Use full path from workspace root
@use 'libs/theme-system/theme-styles/src/lib/styles/index' as *;
// OR if stylePreprocessorOptions is configured:
// @use 'index' as *;

// Global app styles
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
  // ✅ Use CSS variables from theme system
  background-color: var(--ux-background);
  color: var(--ux-on-background);
  line-height: 1.6;
}

// ✅ Minimal global styles - rely on theme utility classes
.app-container {
  // Theme utility classes handle most styling
  &.sidebar-open {
    // Only add styles not available in utilities
    .app-main {
      margin-left: 16rem; // Custom sidebar width
    }
  }
}

// ✅ Focus states for accessibility
button:focus-visible,
a:focus-visible {
  outline: 2px solid var(--ux-primary);
  outline-offset: 2px;
}

Note: If you configure stylePreprocessorOptions (see Step 12), you can use the shorter @use 'index' as *; import.

Step 6: Configure Database Integration (if needed)

For apps requiring local storage, create a database service:

import { Injectable } from '@angular/core';
import Dexie from 'dexie';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class MyAppDbService extends Dexie {
  // Define your tables and models
  // Follow the pattern established in space-map-db.service.ts
}

Step 7: Create Feature Structure

Organize your features following our established patterns:

src/app/
├── core/
│   ├── models/          # Data models and interfaces
│   ├── services/        # Business logic services
│   └── guards/          # Route guards
├── features/            # Feature modules
│   ├── feature-name/
│   │   ├── components/  # Feature-specific components
│   │   ├── services/    # Feature services
│   │   └── models/      # Feature models
└── shared/              # Shared components and utilities
    ├── components/      # Reusable components
    ├── directives/      # Custom directives
    └── pipes/           # Custom pipes

Step 8: Configure Safe Typing

Use SafeAny from @libs-foundational/core-models

Always use SafeAny instead of any:

import type { SafeAny } from '@libs-foundational/core-models';

// Good ✅
interface MyInterface {
  data: SafeAny;
  config: Record<string, SafeAny>;
}

// Avoid ❌
interface BadInterface {
  data: any;
  config: any;
}

Follow Type Safety Guidelines

  1. Import types explicitly:
import type { SafeAny } from '@libs-foundational/core-models';
import type { SelectOption } from '@ux-ui-extended';
  1. Use proper interface definitions:
export interface MyComponentData {
  id: string;
  title: string;
  config?: SafeAny;
}
  1. Avoid circular dependencies by using core-types for shared interfaces.

Step 9: Build Configuration

Configure Build Targets

Ensure your project.json has proper build configuration:

{
  "name": "my-app-name",
  "targets": {
    "build": {
      "executor": "@angular-devkit/build-angular:application",
      "options": {
        "outputPath": "dist/apps/my-app-name",
        "index": "apps/my-app-name/src/index.html",
        "browser": "apps/my-app-name/src/main.ts",
        "polyfills": ["zone.js"],
        "tsConfig": "apps/my-app-name/tsconfig.app.json",
        "assets": [
          "apps/my-app-name/src/favicon.ico",
          "apps/my-app-name/src/assets"
        ],
        "styles": ["apps/my-app-name/src/styles.scss"],
        "scripts": [],
        "budgets": [
          {
            "type": "initial",
            "maximumWarning": "1mb",
            "maximumError": "2mb"
          },
          {
            "type": "anyComponentStyle",
            "maximumWarning": "35kb",
            "maximumError": "50kb"
          }
        ]
      }
    },
    "serve": {
      "executor": "@angular-devkit/build-angular:dev-server",
      "options": {
        "buildTarget": "my-app-name:build"
      },
      "configurations": {
        "production": {
          "buildTarget": "my-app-name:build:production"
        }
      }
    }
  }
}

Step 10: Build Library Dependencies First

CRITICAL: Before building or serving your app, ensure all library dependencies are built:

Build Libraries

# Option 1: Build all libraries (recommended for first-time setup)
yarn build:libs:auto

# Option 2: Build specific libraries your app depends on
yarn nx build core-models
yarn nx build storage-system
yarn nx build theme-system
yarn nx build flash-persistence
yarn nx build ux-ui-core
yarn nx build ux-ui-extended
# ... any other libraries your app imports

Test the Build

  1. Build the application:
npx nx build my-app-name
  1. Serve the application:
npx nx serve my-app-name
  1. Run tests:
npx nx test my-app-name
npx nx lint my-app-name

Build Order Matters

Our monorepo uses a custom library builder (build-angular-library.js) that:

Apps consume libraries from the dist/ directory, so libraries must be built before the app can import them.

Step 11: Integration with UX Framework

Using UX Framework Components

Import and use components from our UX framework:

import { Component } from '@angular/core';
import { UxMultiSelectComponent, SelectOption } from 'libs/ux-ui-extended/src/lib/components';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-my-feature',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    UxMultiSelectComponent
  ],
  template: `
    <ux-multi-select
      [options]="options"
      [(ngModel)]="selectedValues"
      placeholder="Select options..."
      (selectionChange)="onSelectionChange($event)">
    </ux-multi-select>
  `
})
export class MyFeatureComponent {
  options: SelectOption[] = [
    { value: 'option1', label: 'Option 1' },
    { value: 'option2', label: 'Option 2' }
  ];
  
  selectedValues: string[] = [];
  
  onSelectionChange(values: string[]): void {
    this.selectedValues = values;
  }
}

Step 12: Configure SASS Imports for Theme System

You have two options for using theme-system styles in your application:

Option 1: Use Full Paths (Recommended)

Use the complete path from workspace root in your SCSS files:

// apps/my-app-name/src/app/components/my-component/my-component.component.scss
@use 'libs/theme-system/theme-styles/src/lib/styles/index' as *;

.my-component {
  // Use CSS variables from theme system
  background: var(--ux-surface);
  color: var(--ux-on-surface);
  padding: var(--ux-spacing-md);
  border-radius: var(--ux-radius-md);
}

Option 2: Configure Include Paths

Add stylePreprocessorOptions to both build and serve targets in apps/my-app-name/project.json:

{
  "targets": {
    "build": {
      "executor": "@angular-devkit/build-angular:application",
      "options": {
        "stylePreprocessorOptions": {
          "includePaths": ["libs/theme-system/theme-styles/src/lib/styles"]
        },
        "outputPath": "dist/apps/my-app-name",
        "index": "apps/my-app-name/src/index.html",
        "browser": "apps/my-app-name/src/main.ts",
        // ... other options
      }
    },
    "serve": {
      "executor": "@angular-devkit/build-angular:dev-server",
      "options": {
        "stylePreprocessorOptions": {
          "includePaths": ["libs/theme-system/theme-styles/src/lib/styles"]
        }
      }
    }
  }
}

Then use shorter imports:

// With stylePreprocessorOptions configured
@use 'index' as *;

.my-component {
  background: var(--ux-surface);
  color: var(--ux-on-surface);
}

Using Theme Styles

Prefer Utility Classes in templates over custom SCSS:

<!-- ✅ Preferred: Use utility classes -->
<div class="ux-bg-surface ux-p-4 ux-rounded-lg ux-shadow">
  <h2 class="ux-text-lg ux-font-semibold ux-mb-2">Title</h2>
  <button class="ux-btn ux-btn-primary">Action</button>
</div>

Use CSS Variables when custom SCSS is needed:

.custom-element {
  background: var(--ux-surface);  // ✅ Good
  padding: var(--ux-spacing-md);  // ✅ Good
  // background: #ffffff;         // ❌ Bad - hardcoded
}

Important Notes

  1. Server Restart Required: After adding stylePreprocessorOptions, restart your development server
  2. SASS vs TypeScript: TypeScript path mappings (like @theme-system) only work for TypeScript imports, not SASS imports
  3. Consistent Configuration: Ensure the configuration is added to both build and serve targets
  4. Theme Library Names: Use @theme-system-theme-styles when referencing in TypeScript, but use paths in SCSS

Common Issues and Solutions

Build Errors

  1. "Cannot find module '@my-library'"

    • Cause: Library not built or path mapping incorrect
    • Solution: Build the library first with yarn nx build my-library
    • Check: Verify tsconfig.base.json has correct path mapping
    • Note: Apps import from dist/libs/, not libs/ directly
  2. Module Resolution Issues:

    • Ensure all path mappings are correct in tsconfig.base.json
    • Verify library dependencies are built before app build
    • For apps, path mappings should point to dist/libs/my-library
  3. Type Declaration Issues:

    • Use SafeAny from @libs-foundational/core-models instead of any
    • Ensure proper type imports with import type { ... }
    • Import from built libraries in dist/ for production builds
  4. Circular Dependency Issues:

    • Move shared types to @libs-foundational/core-models
    • Avoid importing implementation details between libraries
    • Use dependency injection to break circular dependencies
  5. SCSS Import Errors

    • Use full path: @use 'libs/theme-system/theme-styles/src/lib/styles/index' as *;
    • Or configure stylePreprocessorOptions in project.json
    • Remember: SASS doesn't use TypeScript path mappings

Angular Specific Issues

  1. Standalone Component Issues:

    • Ensure all imports are included in component imports array
    • Use Angular 20 standalone patterns consistently
    • Remember to import CommonModule for @if, @for, etc.
  2. Theme Integration Issues:

    • Import theme styles in correct order
    • Use CSS variables provided by theme system
    • Verify theme providers are in main.ts
  3. "inject() must be called from an injection context"

    • Cause: Calling inject() outside of constructor or field initializer
    • Solution: Use constructor injection or field initializers only
    • Example: private storage = inject(StorageService);

Storage API Errors

  1. "storage.asSignal is not a function"

    • Cause: Outdated API usage
    • Solution: Use manual signals with storage.get() and storage.set()
    • Example: See updated AppComponent in Step 5
  2. "Argument of type '{ prefix: string }' is not assignable"

    • Cause: Incorrect storage.list() usage
    • Solution: Use storage.list(prefix?) with optional string prefix
    • Correct: await storage.list('app:');
    • Wrong: await storage.list({ prefix: 'app:' });
  3. "Property 'getAdapterKind' does not exist"

    • Cause: Using old API
    • Solution: Access directly: storage.adapterKind (no function call)

Runtime Issues

  1. State not persisting:

    • Verify provideFlashPersistenceWithNGXS() is in main.ts
    • Check namespace configuration
    • Ensure states are properly decorated with @State
  2. Theme not loading:

    • Verify provideThemeSystem() is in main.ts
    • Check if INSTANT Theme Loader is configured
    • Ensure theme CSS variables are available
  3. Cross-tab sync not working:

    • Verify StorageService is provided correctly
    • Check browser supports OPFS/IndexedDB
    • Test in non-incognito windows

Development Server Issues

  1. Port already in use:

    # Use a different port
    npx nx serve my-app-name --port 4700
    
  2. Hot reload not working:

    • Restart dev server after project.json changes
    • Restart after adding stylePreprocessorOptions
    • Check for TypeScript errors blocking compilation

Examples

Space-Map App Example

The space-map application serves as a perfect example of this pattern:

Key Files to Reference

  1. apps/space-map/src/app/app.component.ts - Main app component
  2. apps/space-map/src/app/core/services/space-map-db.service.ts - Database service
  3. apps/space-map/src/app/features/space-editor/components/create-page-dialog/ - Feature component

Best Practices

  1. Always use yarn for package management
  2. Follow TypeScript strict typing with SafeAny when needed
  3. Use Angular 20 standalone components throughout
  4. Leverage theme system for consistent styling
  5. Build dependencies first before building apps
  6. Test incrementally as you add features
  7. Document as you go to maintain clarity

Ports and Development

Standard Port Assignments

Running Multiple Apps

When developing, you can run multiple apps simultaneously:

# Terminal 1
npx nx serve ux-test

# Terminal 2  
npx nx serve editor-web

# Terminal 3
npx nx serve my-app-name --port 4700

🌟 APP INTEGRATION SUMMARY ⭐

Every new app you create now has instant access to all our core systems:

🧠 Flash-Persistence Integration

// App state with automatic persistence
readonly isAppReady = this.store.select(AppState.isReady);
readonly currentTheme = this.store.select(AppState.theme);
this.store.dispatch({ type: 'SetTheme', payload: 'dark' });

🗄️ Storage-System Integration

// Manual signals backed by storage
private readonly _userPreferences = signal<AppPreferences>({
  enableNotifications: true,
  sidebarCollapsed: false,
  language: 'en'
});
readonly userPreferences = computed(() => this._userPreferences());

// Load from storage
async loadPreferences() {
  const prefs = await this.storage.get<AppPreferences>('app:user-preferences');
  if (prefs) this._userPreferences.set(prefs);
}

// Save to storage - updates across tabs automatically
await this.storage.set('app:user-preferences', newPreferences);

🎨 Theme-System Integration

// Theme system integration
@use 'libs/theme-system/theme-styles/src/lib/styles/index' as *;
// OR with stylePreprocessorOptions configured:
// @use 'index' as *;

.app-header {
  // Use CSS variables from theme
  background: var(--ux-surface);
  padding: var(--ux-spacing-md);
  border-bottom: 1px solid var(--ux-outline-variant);
  
  .header-title {
    color: var(--ux-on-surface);
    font-size: var(--ux-text-lg);
    font-weight: 600;
  }
}

🏗️ Modern App Architecture

🎯 What You Get Out of the Box

  1. 🎨 Complete Theme System: Light/Dark/Auto switching with persistence
  2. 💾 Smart State Management: App state persists automatically
  3. ⚡ Real-time Updates: Changes sync across browser tabs instantly
  4. 📱 Responsive Design: Mobile-first grid system and breakpoints
  5. ♿ Full Accessibility: ARIA labels, keyboard navigation, screen reader support
  6. 🧠 Intelligent Caching: User preferences cached with AI optimization
  7. 🔄 Cross-tab Sync: Settings changes reflected immediately in other tabs
  8. 📊 Performance Monitoring: Built-in metrics and performance tracking

🚀 Advanced Features Included

This comprehensive guide ensures that new applications follow our established patterns and integrate seamlessly with our monorepo architecture.