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(), useset/getmethods 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-modelsusage (not@core-types)
Prerequisites
Before creating a new app, ensure you have:
- Node.js and yarn installed
- NX CLI available globally or locally
- Understanding of our monorepo architecture
- Access to our core libraries (
@libs-foundational/core-models,@storage-system,@theme-system,@flash-persistence) - All library dependencies built (see Step 10 for build order)
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:
- Style:
scss(SCSS for styling) - Linter:
eslint(ESLint for code quality) - Unit Test Runner:
jest(Jest for testing) - E2E Test Runner:
cypress(Cypress for end-to-end testing) - Setup Tailwind:
yes(Enable Tailwind CSS) - Standalone:
yes(Use Angular 20 standalone components)
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
- Import types explicitly:
import type { SafeAny } from '@libs-foundational/core-models';
import type { SelectOption } from '@ux-ui-extended';
- Use proper interface definitions:
export interface MyComponentData {
id: string;
title: string;
config?: SafeAny;
}
- 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
- Build the application:
npx nx build my-app-name
- Serve the application:
npx nx serve my-app-name
- 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:
- Ensures proper TypeScript decorator support
- Handles complex path mappings
- Manages SCSS compilation with theme integration
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
- Server Restart Required: After adding
stylePreprocessorOptions, restart your development server - SASS vs TypeScript: TypeScript path mappings (like
@theme-system) only work for TypeScript imports, not SASS imports - Consistent Configuration: Ensure the configuration is added to both
buildandservetargets - Theme Library Names: Use
@theme-system-theme-styleswhen referencing in TypeScript, but use paths in SCSS
Common Issues and Solutions
Build Errors
"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.jsonhas correct path mapping - Note: Apps import from
dist/libs/, notlibs/directly
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
- Ensure all path mappings are correct in
Type Declaration Issues:
- Use
SafeAnyfrom@libs-foundational/core-modelsinstead ofany - Ensure proper type imports with
import type { ... } - Import from built libraries in
dist/for production builds
- Use
Circular Dependency Issues:
- Move shared types to
@libs-foundational/core-models - Avoid importing implementation details between libraries
- Use dependency injection to break circular dependencies
- Move shared types to
SCSS Import Errors
- Use full path:
@use 'libs/theme-system/theme-styles/src/lib/styles/index' as *; - Or configure
stylePreprocessorOptionsinproject.json - Remember: SASS doesn't use TypeScript path mappings
- Use full path:
Angular Specific Issues
Standalone Component Issues:
- Ensure all imports are included in component
importsarray - Use Angular 20 standalone patterns consistently
- Remember to import
CommonModulefor@if,@for, etc.
- Ensure all imports are included in component
Theme Integration Issues:
- Import theme styles in correct order
- Use CSS variables provided by theme system
- Verify theme providers are in
main.ts
"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);
- Cause: Calling
Storage API Errors
"storage.asSignal is not a function"
- Cause: Outdated API usage
- Solution: Use manual signals with
storage.get()andstorage.set() - Example: See updated AppComponent in Step 5
"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:' });
- Cause: Incorrect
"Property 'getAdapterKind' does not exist"
- Cause: Using old API
- Solution: Access directly:
storage.adapterKind(no function call)
Runtime Issues
State not persisting:
- Verify
provideFlashPersistenceWithNGXS()is inmain.ts - Check namespace configuration
- Ensure states are properly decorated with
@State
- Verify
Theme not loading:
- Verify
provideThemeSystem()is inmain.ts - Check if INSTANT Theme Loader is configured
- Ensure theme CSS variables are available
- Verify
Cross-tab sync not working:
- Verify StorageService is provided correctly
- Check browser supports OPFS/IndexedDB
- Test in non-incognito windows
Development Server Issues
Port already in use:
# Use a different port npx nx serve my-app-name --port 4700Hot reload not working:
- Restart dev server after
project.jsonchanges - Restart after adding
stylePreprocessorOptions - Check for TypeScript errors blocking compilation
- Restart dev server after
Examples
Space-Map App Example
The space-map application serves as a perfect example of this pattern:
- Location:
apps/space-map/ - Dependencies:
@core-types,@ux-framework - Features: Database integration, component usage, theme integration
- Architecture: Standalone components, proper typing, modular structure
Key Files to Reference
apps/space-map/src/app/app.component.ts- Main app componentapps/space-map/src/app/core/services/space-map-db.service.ts- Database serviceapps/space-map/src/app/features/space-editor/components/create-page-dialog/- Feature component
Best Practices
- Always use yarn for package management
- Follow TypeScript strict typing with SafeAny when needed
- Use Angular 20 standalone components throughout
- Leverage theme system for consistent styling
- Build dependencies first before building apps
- Test incrementally as you add features
- Document as you go to maintain clarity
Ports and Development
Standard Port Assignments
ux-test: 5300editor-web: 5200space-map: 4600+ (or next available)docs: 5010
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-wide state management with automatic persistence
- NGXS compatibility for existing patterns
- Real-time updates across all components
- Theme preferences persist automatically
// 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
- User preferences with intelligent caching
- Settings synchronization across browser tabs
- Simple API with
set(),get(),list(),clear() - Automatic OPFS/IndexedDB selection for optimal performance
// 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
- Dynamic theme switching (Light/Dark/Auto)
- CSS variables throughout the app
- Responsive design system built-in
- Consistent typography and spacing
// 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
- ✅ Grid-based layouts for responsive design
- ✅ Accessibility-first navigation and interactions
- ✅ Font Awesome icons throughout
- ✅ Loading states and progressive enhancement
- ✅ Real-time synchronization indicators
- ✅ Modern control flow (
@if,@for,@switch)
🎯 What You Get Out of the Box
- 🎨 Complete Theme System: Light/Dark/Auto switching with persistence
- 💾 Smart State Management: App state persists automatically
- ⚡ Real-time Updates: Changes sync across browser tabs instantly
- 📱 Responsive Design: Mobile-first grid system and breakpoints
- ♿ Full Accessibility: ARIA labels, keyboard navigation, screen reader support
- 🧠 Intelligent Caching: User preferences cached with AI optimization
- 🔄 Cross-tab Sync: Settings changes reflected immediately in other tabs
- 📊 Performance Monitoring: Built-in metrics and performance tracking
🚀 Advanced Features Included
- Sidebar navigation with state persistence
- Theme switching with animated transitions
- Loading overlays with proper accessibility
- Notification system with count tracking
- Multi-language support ready
- Progressive enhancement patterns
This comprehensive guide ensures that new applications follow our established patterns and integrate seamlessly with our monorepo architecture.