Add New Sub Library
Documentation for Developers
How to Add a New Component (or Sub-Library) to the Monorepo
This guide documents the complete process of adding a new library to our NX monorepo, following the established patterns and architecture principles.
Prerequisites
Before creating a new component (or sub-library), ensure you have:
- Understanding of our library dependency hierarchy
- Knowledge of our typing system (
@core) - Familiarity with our build and publishing process
Component Helper Libraries for our Library or Sub-Library (component)
@widgets - Widget system
Dependencies: @core
@gograb-ai - AI integration
This library will help our component to be more intelligent
@storage-system - Storage system
This library will help our component to store data
@theme-system - Theme system
This library will help our component to be themed
Step 1: Generate the Library
Use the NX Angular library generator:
npx nx g @nx/angular:library my-library-name
Configuration Options
When prompted, use these settings:
- Style:
scss(SCSS for styling) - Linter:
eslint(ESLint for code quality) - Unit Test Runner:
jest(Jest for testing) - Publishable:
yes(if library will be published) - Import Path:
@my-library-name(consistent naming)
Step 2: Configure Library Structure
Standard Library Structure
libs/my-library-name/
├── src/
│ ├── lib/
│ │ ├── components/ # Angular components
│ │ │ ├── index.ts # Add barrel file for components
│ │ ├── services/ # Injectable services
│ │ │ ├── index.ts # Add barrel file for services
│ │ ├── models/ # Type definitions
│ │ │ ├── index.ts # Add barrel file for models
│ │ ├── directives/ # Custom directives
│ │ │ ├── index.ts # Add barrel file for directives
│ │ ├── pipes/ # Custom pipes
│ │ │ ├── index.ts # Add barrel file for pipes
│ │ ├── utils/ # Utility functions
│ │ └── index.ts # Internal exports
│ └── index.ts # Public API entry point
├── project.json # NX project configuration
├── tsconfig.json # TypeScript configuration
├── tsconfig.lib.json # Library-specific TypeScript config
├── tsconfig.spec.json # Test TypeScript config
├── jest.config.ts # Jest configuration
├── README.md # Library documentation
└── package.json # Package configuration
Step 3: Configure TypeScript and Build Settings
Update tsconfig.base.json
Add your library to the path mapping:
{
"compilerOptions": {
"paths": {
// ... existing paths ...
"@my-library-name": ["libs/my-library-name/src/index.ts"],
"@my-library-name/*": ["libs/my-library-name/src/*"]
}
}
}
Configure Library TypeScript (tsconfig.lib.json)
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": [
"src/**/*.spec.ts",
"src/**/*.test.ts",
"jest.config.ts",
"src/**/*.stories.ts"
]
}
Configure Build Target (project.json)
{
"name": "my-library-name",
"sourceRoot": "libs/my-library-name/src",
"prefix": "lib",
"targets": {
"build": {
"executor": "@nx/angular:ng-packagr-lite",
"outputs": ["dist/libs/my-library-name"],
"options": {
"project": "libs/my-library-name/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/my-library-name/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "libs/my-library-name/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["coverage/libs/my-library-name"],
"options": {
"jestConfig": "libs/my-library-name/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["eslint-report.json"],
"options": {
"lintFilePatterns": [
"libs/my-library-name/**/*.ts",
"libs/my-library-name/**/*.html"
]
}
}
},
"tags": ["npm:public"]
}
Step 4: Configure Package for Publishing
Create ng-package.json
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/my-library-name",
"lib": {
"entryFile": "src/index.ts"
}
}
Create package.json
{
"name": "@my-library-name",
"version": "0.0.1",
"description": "Description of my library",
"keywords": ["angular", "library", "typescript"],
"author": "GoGrab Space",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/gograb-space/editor.git",
"directory": "libs/my-library-name"
},
"peerDependencies": {
"@angular/common": "^18.0.0",
"@angular/core": "^18.0.0"
},
"dependencies": {
"tslib": "^2.3.0"
},
"sideEffects": false,
"main": "index.js",
"module": "fesm2022/my-library-name.mjs",
"typings": "index.d.ts"
}
Step 5: Integrate Core Systems (ESSENTIAL) ⭐
Before implementing any logic, configure integration with our three core systems:
🧠 Flash-Persistence Integration
Add state management with NGXS compatibility:
// libs/my-library-name/src/lib/providers/my-library.providers.ts
import { EnvironmentProviders } from '@angular/core';
import { provideFlashPersistenceWithNGXS } from '@flash-persistence/ngxs-compat';
import { MyLibraryState } from '../state/my-library.state';
export function provideMyLibrary(): EnvironmentProviders {
return provideFlashPersistenceWithNGXS({
states: [MyLibraryState],
namespace: 'my-library',
devMode: !environment.production
});
}
🗄️ Storage-System Integration
Configure intelligent storage:
// libs/my-library-name/src/lib/services/my-library.service.ts
import { Injectable, inject } from '@angular/core';
import { StorageService } from '@storage-system';
import { signal, computed } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class MyLibraryService {
private readonly storage = inject(StorageService);
// Reactive storage-backed signals
readonly config = this.storage.asSignal<MyLibraryConfig>('my-library:config', {
theme: 'default',
enabled: true
}).value;
// Computed properties based on storage
readonly isReady = computed(() => this.config() !== null);
async updateConfig(config: Partial<MyLibraryConfig>): Promise<void> {
const current = this.config();
await this.storage.put('my-library:config', { ...current, ...config });
}
}
🎨 Theme-System Integration [[memory:6454111]]
Follow our established three-library architecture:
// libs/my-library-name/src/lib/providers/theme.providers.ts
// ✅ For production libraries (most common)
import {
ThemeApplicationService,
ThemeManagerService,
ThemeStorageService
} from '@theme-runtime';
// ✅ OR for development/creation libraries
import {
ThemeEditorComponent,
ThemeDebuggerComponent,
ThemeManagerService // Also available here
} from '@theme-dev-tools';
// ✅ OR for compatibility (always works)
import { ThemeManagerService } from '@theme-system';
// Add theme provider
import { provideUnifiedThemeSystemWithSync } from '@theme-runtime';
export function provideLibraryTheme() {
return [
provideUnifiedThemeSystemWithSync({
enableSync: true,
autoSync: true
})
];
}
Library SCSS Integration:
// libs/my-library-name/src/lib/styles/index.scss
// ✅ CORRECT: Use direct path with proper module resolution
@use '@theme-styles/src/lib/styles/index' as *;
// Your component styles using theme variables
.my-component {
// ✅ Use proper --ux- prefixed variables
background: var(--ux-surface);
color: var(--ux-on-surface);
border: 1px solid var(--ux-outline);
padding: var(--ux-spacing-md);
border-radius: var(--ux-radius-md);
// ✅ Use theme utility classes when possible
@extend .ux-shadow;
@extend .ux-transition;
}
Step 6: Implement Safe Typing with Models Directory
ALWAYS Create Models Directory First
# Create models directory structure
mkdir -p libs/my-library-name/src/lib/models
mkdir -p libs/my-library-name/src/lib/state
mkdir -p libs/my-library-name/src/lib/providers
touch libs/my-library-name/src/lib/models/index.ts
touch libs/my-library-name/src/lib/models/my-library.types.ts
touch libs/my-library-name/src/lib/state/my-library.state.ts
Define All Types in Models Directory
// libs/my-library-name/src/lib/models/my-library.types.ts
import type { SafeAny } from '@libs-foundational/core-models';
export interface MyLibraryConfig {
name: string;
theme: 'light' | 'dark' | 'auto';
enabled: boolean;
options: Record<string, SafeAny>;
storage?: {
namespace?: string;
strategy?: 'speed' | 'balanced' | 'durable';
};
}
export interface MyLibraryState {
config: MyLibraryConfig;
isInitialized: boolean;
error?: string;
}
export interface MyComponentProps {
data: MyLibraryConfig;
disabled?: boolean;
className?: string;
}
Create Flash-Persistence State
// libs/my-library-name/src/lib/state/my-library.state.ts
import { State, Action, Selector } from '@flash-persistence/ngxs-compat';
import { MyLibraryState, MyLibraryConfig } from '../models';
@State<MyLibraryState>({
name: 'myLibrary',
defaults: {
config: {
name: 'MyLibrary',
theme: 'auto',
enabled: true,
options: {}
},
isInitialized: false
}
})
export class MyLibraryState {
@Selector()
static config(state: MyLibraryState): MyLibraryConfig {
return state.config;
}
@Selector()
static isReady(state: MyLibraryState): boolean {
return state.isInitialized && state.config.enabled;
}
@Action({ type: 'UpdateConfig' })
updateConfig(ctx: any, config: Partial<MyLibraryConfig>) {
const state = ctx.getState();
ctx.setState({
...state,
config: { ...state.config, ...config }
});
}
@Action({ type: 'Initialize' })
initialize(ctx: any) {
const state = ctx.getState();
ctx.setState({
...state,
isInitialized: true
});
}
}
Export from Models Index
// libs/my-library-name/src/lib/models/index.ts
export * from './my-library.types';
Import from Models in Services/Components
// libs/my-library-name/src/lib/services/my-service.ts
import { MyLibraryConfig } from '../models'; // ← Use relative import from models
// libs/my-library-name/src/lib/components/my-component.ts
import { MyComponentProps } from '../models'; // ← Use relative import from models
Avoid Circular Dependencies
- Don't import from libraries at the same level
- Use core for shared interfaces
- Follow dependency hierarchy: Core → UI → Feature → App
// Good ✅
import type { SafeAny } from '@libs-foundational/core-models';
// Avoid ❌ - Don't import from peer libraries
// import { SomeType } from '@other-library';
Step 6: Create Public API
Define Public Exports (src/index.ts)
// Export all public models FIRST (other modules depend on these)
export * from './lib/models';
// Export all public components
export * from './lib/components';
// Export all public services
export * from './lib/services';
// Export all public directives
export * from './lib/directives';
// Export all public pipes
export * from './lib/pipes';
// Export utilities if public
export * from './lib/utils/public-utils';
// Re-export types from core for convenience
export type { SafeAny } from '@libs-foundational/core-models';
Component Export Example
// libs/my-library-name/src/lib/components/index.ts
export { MyComponent } from './my-component/my-component.component';
export { AnotherComponent } from './another-component/another-component.component';
// Export component types
export type { MyComponentProps } from '../models/my-component-props';
Step 7: Implement Modern Components with Full Integration
Modern Standalone Component with Full Theme Integration [[memory:6454111]]
// libs/my-library-name/src/lib/components/my-component/my-component.component.ts
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, inject, signal, computed, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Store } from '@flash-persistence/ngxs-compat';
import { StorageService } from '@storage-system';
import { ThemeManagerService } from '@theme-system'; // ✅ Compatibility layer
import type { SafeAny } from '@libs-foundational/core-models';
import { MyComponentConfig, MyLibraryState } from '../../models';
import { MyLibraryService } from '../../services/my-library.service';
@Component({
selector: 'lib-my-component',
standalone: true,
imports: [CommonModule],
template: `
<div
class="my-component ux-bg-surface ux-text-on-surface ux-p-4 ux-rounded-lg ux-shadow"
[class.ux-opacity-50]="disabled()"
[class.ux-bg-primary]="isReady()"
>
@if (isReady()) {
<div class="component-header ux-flex ux-items-center ux-space-x-3 ux-mb-4">
@if (config().icon) {
<i [class]="'fa fa-' + config().icon" class="ux-text-primary" aria-hidden="true"></i>
}
<h3 class="ux-text-lg ux-font-semibold ux-text-on-surface">{{ config().title }}</h3>
</div>
<div class="component-content ux-mb-4">
<ng-content></ng-content>
</div>
<div class="component-actions ux-flex ux-space-x-2">
@for (action of actions(); track action.id) {
<button
type="button"
class="ux-btn ux-btn-primary ux-px-4 ux-py-2 ux-rounded ux-transition"
[class.ux-opacity-50]="disabled() || action.disabled"
[disabled]="disabled() || action.disabled"
(click)="handleAction(action)"
[attr.aria-label]="action.label"
>
{{ action.label }}
</button>
}
</div>
} @else {
<div class="component-loading ux-flex ux-items-center ux-justify-center ux-p-8">
<i class="fa fa-spinner fa-spin ux-text-primary ux-mr-2" aria-hidden="true"></i>
<span class="ux-text-on-surface">Loading...</span>
</div>
}
</div>
`,
styleUrls: ['./my-component.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent implements OnInit {
private readonly store = inject(Store);
private readonly storage = inject(StorageService);
private readonly libraryService = inject(MyLibraryService);
// Inputs with signals
private _config = signal<MyComponentConfig>({
title: 'Default Title',
icon: 'cog',
actions: []
});
@Input() set config(value: MyComponentConfig) {
this._config.set(value);
}
private _disabled = signal(false);
@Input() set disabled(value: boolean) {
this._disabled.set(value);
}
// Outputs
@Output() actionTriggered = new EventEmitter<SafeAny>();
@Output() configChanged = new EventEmitter<MyComponentConfig>();
// Computed properties
config = computed(() => this._config());
disabled = computed(() => this._disabled());
// State selectors with signals
readonly libraryState = this.store.select(MyLibraryState.config);
readonly isReady = computed(() => this.libraryService.isReady());
// Storage-backed configuration
readonly userPreferences = this.storage.asSignal<SafeAny>('my-component:preferences', {}).value;
// Theme integration
readonly themeStyles = computed(() => {
const theme = this.libraryState()?.theme || 'auto';
const preferences = this.userPreferences();
return {
'--component-theme': theme,
'--component-accent': preferences.accentColor || 'var(--ux-primary)'
};
});
// Actions computed from config and state
readonly actions = computed(() => {
const baseActions = this.config().actions || [];
const isEnabled = this.libraryState()?.enabled;
return baseActions.map(action => ({
...action,
disabled: !isEnabled || action.disabled
}));
});
async ngOnInit(): Promise<void> {
// Initialize component with state
await this.libraryService.initialize();
}
async handleAction(action: SafeAny): Promise<void> {
if (this.disabled() || action.disabled) return;
// Store action in intelligent storage
await this.storage.put(`my-component:last-action:${action.id}`, {
action: action.id,
timestamp: new Date().toISOString(),
config: this.config()
});
// Emit action event
this.actionTriggered.emit({
action: action.id,
data: action.data,
timestamp: new Date()
});
// Dispatch to state if needed
if (action.updateState) {
this.store.dispatch({ type: 'UpdateConfig', payload: action.stateUpdate });
}
}
}
Models Example (Required)
// libs/my-library-name/src/lib/models/my-component.types.ts
import type { SafeAny } from '@libs-foundational/core-models';
export interface MyComponentConfig {
title: string;
data: SafeAny;
options?: Record<string, SafeAny>;
}
Corresponding SCSS File:
// libs/my-library-name/src/lib/components/my-component/my-component.component.scss
// ✅ CORRECT: Use direct theme-system import
@use '@theme-styles/src/lib/styles/index' as *;
:host {
display: block;
width: 100%;
.my-component {
// ✅ Minimal custom styles - rely on theme utility classes in template
position: relative;
&.ux-bg-primary {
// Enhanced styling when ready
transform: translateY(-1px);
box-shadow: var(--ux-shadow-lg);
}
// Only add styles not available in theme utilities
.component-loading {
min-height: 120px; // Custom min-height for loading state
}
}
}
Key Theme Integration Principles:
- ✅ Use utility classes in template (ux-bg-surface, ux-text-primary, etc.)
- ✅ CSS Variables for values (var(--ux-spacing-md), var(--ux-primary))
- ✅ Minimal custom SCSS - only what's not available in utilities
- ✅ Proper theme import (@use '@theme-styles/src/lib/styles/index')
Service Example
// libs/my-library-name/src/lib/services/my-service.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import type { SafeAny } from '@libs-foundational/core-models';
import { MyServiceConfig } from '../models'; // ← Import from models, NO local types!
@Injectable({
providedIn: 'root'
})
export class MyService {
private configSubject = new BehaviorSubject<MyServiceConfig | null>(null);
private dataSubject = new BehaviorSubject<SafeAny[]>([]);
config$ = this.configSubject.asObservable();
data$ = this.dataSubject.asObservable();
initialize(config: MyServiceConfig): void {
this.configSubject.next(config);
}
async loadData(): Promise<SafeAny[]> {
const config = this.configSubject.value;
if (!config) {
throw new Error('Service not initialized');
}
try {
// Implementation logic here
const data: SafeAny[] = [];
this.dataSubject.next(data);
return data;
} catch (error) {
console.error('Failed to load data:', error);
throw error;
}
}
updateData(newData: SafeAny[]): void {
this.dataSubject.next(newData);
}
}
Service Types (In Models Directory)
// libs/my-library-name/src/lib/models/my-service.types.ts
export interface MyServiceConfig {
apiUrl: string;
timeout?: number;
retries?: number;
}
Step 8: Testing Setup
Configure Jest (jest.config.ts)
export default {
displayName: 'my-library-name',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../coverage/libs/my-library-name',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
Component Test Example
// libs/my-library-name/src/lib/components/my-component/my-component.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent, MyComponentConfig } from './my-component.component';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
const mockConfig: MyComponentConfig = {
title: 'Test Component',
data: { test: true }
};
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MyComponent]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
component.config = mockConfig;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display the title', () => {
const compiled = fixture.nativeElement;
expect(compiled.querySelector('h3').textContent).toContain('Test Component');
});
it('should emit actionTriggered when clicked', () => {
spyOn(component.actionTriggered, 'emit');
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(component.actionTriggered.emit).toHaveBeenCalled();
});
});
Step 9: Documentation
🚨 CRITICAL: No Local Types Policy
NEVER define types/interfaces locally in service or component files!
✅ Correct Pattern: Use Models Directory
// ✅ GOOD: In libs/my-library/src/lib/models/my-types.ts
export interface MyComponentConfig {
title: string;
data: SafeAny;
}
// ✅ GOOD: In libs/my-library/src/lib/services/my-service.ts
import { MyComponentConfig } from '../models/my-types';
❌ Wrong Pattern: Local Type Definitions
// ❌ BAD: Local interface in service file
interface MyComponentConfig { // <- Never do this!
title: string;
data: SafeAny;
}
Required Structure: Models Directory
Every library MUST have a models/ directory:
libs/my-library-name/
├── src/
│ ├── lib/
│ │ ├── components/
│ │ ├── services/
│ │ ├── models/ # ← REQUIRED
│ │ │ ├── my-types.ts # ← All types go here
│ │ │ └── index.ts # ← Export all types
│ │ └── index.ts
│ └── index.ts # ← Export models (library entry point)
Create README.md
# @my-library-name
Brief description of what this library does.
## Installation
```bash
yarn add @my-library-name
Usage
Basic Usage
import { MyComponent, MyService } from '@my-library-name';
// Component usage
@Component({
imports: [MyComponent],
template: `
<lib-my-component
[config]="myConfig"
(actionTriggered)="onAction($event)">
</lib-my-component>
`
})
export class MyAppComponent {
myConfig = {
title: 'Hello World',
data: { example: true }
};
onAction(event: any): void {
console.log('Action triggered:', event);
}
}
API Reference
Components
MyComponent
| Property | Type | Description |
|---|---|---|
config |
MyComponentConfig |
Configuration object |
disabled |
boolean |
Whether component is disabled |
Services
MyService
Methods for managing data and configuration.
Development
See the monorepo documentation for development guidelines.
## Step 10: Build and Testing
### Build the Library
```bash
# Build the library
npx nx build my-library-name
# Build with dependencies
npx nx build my-library-name --with-deps
Test the Library
# Run tests
npx nx test my-library-name
# Run tests with coverage
npx nx test my-library-name --coverage
# Run linting
npx nx lint my-library-name
Verify Library Integration
Create a test component in an app to verify the library works:
// In any app component
import { MyComponent } from '@my-library-name';
@Component({
imports: [MyComponent],
template: `<lib-my-component [config]="testConfig"></lib-my-component>`
})
export class TestComponent {
testConfig = {
title: 'Library Test',
data: { working: true }
};
}
Step 11: Dependency Management
Understanding Library Dependencies
core (Foundation)
- No dependencies
- Contains all shared type definitions
- Used by all other libraries
Core Libraries
- Can depend on core
- Should not depend on other core libraries
UI Libraries
- Can depend on core and core libraries
- Should not depend on feature libraries
Dependency Declaration
In your library's project.json, declare dependencies:
{
"implicitDependencies": ["core-models"],
"tags": ["npm:public", "scope:ui", "type:library"]
}
Common Patterns and Best Practices
1. Consistent Naming
- Use kebab-case for library names
- Use PascalCase for components and services
- Use camelCase for properties and methods
2. Type Safety
- Always use
SafeAnyinstead ofany - Define clear interfaces for all public APIs
- Use type imports for type-only imports
3. Component Design
- Use standalone components (Angular 20+)
- Implement OnPush change detection
- Provide clear input/output interfaces
- Support content projection when appropriate
4. Service Design
- Use reactive patterns with RxJS
- Provide clear initialization methods
- Handle errors gracefully
- Use dependency injection properly
5. Testing
- Write unit tests for all public APIs
- Use TestBed for component testing
- Mock external dependencies
- Achieve good test coverage
Examples in Our Monorepo
core Library
Prompt-System Library (new)
Location:
prompt-system/Purpose: Centralize prompt-related UI and services (global modal host + open directives)
Dependencies:
@core,@gograb-aiExports:
PromptSystemComponent,PromptModalService,OpenPromptManagerDirective,OpenPromptLabDirectiveIntegration:
- Add
<lib-prompt-system></lib-prompt-system>once to an app root template - Trigger with
[openPromptManager]or[openPromptLab]on any element
- Add
Location:
libs/Purpose: Shared type definitions
Dependencies: None (foundation library)
Exports: Types, interfaces, and utilities
Troubleshooting
Build Issues
Circular Dependency Errors
- Move shared types to
@core - Avoid peer library imports
- Move shared types to
Type Resolution Issues
- Verify path mappings in
tsconfig.base.json - Ensure proper export declarations
- Verify path mappings in
Build Order Issues
- Build dependencies first
- Use
--with-depsflag when needed
Testing Issues
Import Errors in Tests
- Verify test setup configuration
- Check jest configuration
Mock Issues
- Use proper Angular testing utilities
- Mock external dependencies
🌟 INTEGRATION SUMMARY ⭐
Every new library you create now automatically benefits from our three core systems:
🧠 Flash-Persistence Integration
- Zero-config state management with NGXS compatibility
- Instant persistence with automatic hydration
- Type-safe selectors and actions
- Cross-component reactivity with signals
// Automatic state persistence and reactivity
readonly libraryState = this.store.select(MyLibraryState.config);
this.store.dispatch({ type: 'UpdateConfig', payload: newConfig });
🗄️ Storage-System Integration
- Intelligent caching with sub-10ms operations
- AI-powered storage decisions (Speed/Balanced/Durable)
- Cross-tab synchronization out of the box
- Reactive signals backed by persistent storage
// Reactive storage with intelligent caching
readonly userPreferences = this.storage.asSignal<Config>('my-lib:config', defaults).value;
await this.storage.put('my-lib:config', newConfig); // Signal updates automatically!
🎨 Theme-System Integration
- Automatic theme inheritance with CSS variables
- Responsive design utilities built-in
- Accessibility-first components
- Dark/Light mode support automatic
// Automatic theme integration
@use 'index' as theme;
.my-component {
@include theme.ux-card(true);
@include theme.ux-typography('heading', 'lg');
color: var(--ux-on-surface);
}
🚀 Modern Angular 20+ Patterns
- ✅ Standalone components everywhere
- ✅ Signals for reactivity (
signal,computed,effect) - ✅ Modern control flow (
@if,@for,@switch) - ✅ Font Awesome icons as standard
- ✅ Accessibility attributes built-in
- ✅ No timeouts - everything is reactive
- ✅ SOLID principles throughout
💡 Benefits You Get Immediately
- 🔥 Performance: Sub-10ms operations with intelligent caching
- 🧠 Intelligence: AI-powered storage decisions
- 🎨 Consistency: Automatic theme integration
- ⚡ Reactivity: Real-time updates across components
- 💾 Persistence: Zero-config state preservation
- 🌐 Sync: Cross-tab synchronization
- 📱 Responsive: Mobile-first design system
- ♿ Accessible: WCAG-compliant components
This comprehensive guide ensures that new libraries follow our established patterns and integrate seamlessly with our monorepo architecture.
🚨 Quick Troubleshooting Reference
If you encounter import/binding issues after creating a new library:
- "Can't bind to directive" errors → See Angular Import Issues
- Module resolution failures → Use Direct Library Paths
- Event handler type errors → Use
$any($event)in templates
For detailed troubleshooting, see: Imports & Exports Guide
Integration in Applications
// In your application's global styles
@import '@theme-system/styles/themes';
@import '@theme-system/styles/utilities';
// Use theme mixins
.my-component {
@include ux-card(true);
.my-button {
@include ux-button('primary');
}
@include ux-responsive(md) {
padding: ux-spacing('lg');
}
}
Step 8: Configure SASS Imports for Theme System (Optional)
If your library needs to use theme-system styles, configure SASS import paths:
Update Library project.json
Add stylePreprocessorOptions to your library's build configuration in libs/my-library-name/project.json:
{
"targets": {
"build": {
"executor": "@nx/angular:ng-packagr-lite",
"options": {
"stylePreprocessorOptions": {
"includePaths": ["libs/theme-system/src/lib/styles"]
},
"project": "libs/my-library-name/ng-package.json"
}
}
}
}
Using Theme Styles in Library Components
Once configured, you can import theme-system styles in your library component SCSS files:
// libs/my-library-name/src/lib/components/my-component/my-component.component.scss
@use 'index' as theme;
.my-library-component {
// Use theme variables
background: var(--ux-surface);
color: var(--ux-on-surface);
padding: var(--ux-spacing-md);
border-radius: var(--ux-border-radius);
// Use theme mixins
@include theme.ux-card(true);
.component-button {
@include theme.ux-button('primary');
}
// Responsive design
@include theme.ux-responsive(md) {
padding: var(--ux-spacing-lg);
}
}
Alternative Approach for Libraries
If you prefer not to configure stylePreprocessorOptions in the library, you can use relative paths:
// Using relative path (less maintainable but works without configuration)
@use '../../../../../theme-system/src/lib/styles/index' as theme;
.my-component {
@include theme.ux-card();
}
Important Notes for Library Development
- Theme System Dependency: If using theme-system styles, add
@theme-systemas a dependency - Consumer Configuration: Applications using your library will need their own
stylePreprocessorOptionsconfiguration - Documentation: Document theme-system usage requirements for library consumers
- Fallback Styles: Consider providing fallback styles for when theme-system is not available
Step 9: Testing
// ... existing code ...