Converting NX Apps to Libraries
Complete guide for converting Angular apps to reusable libraries in the monorepo
🔄 Converting NX Apps to Libraries
🎯 Overview
This guide covers the complete process of converting existing NX Angular applications into reusable libraries that can be integrated into a unified application architecture. This is particularly useful for monorepo consolidation and creating feature libraries.
🔍 Key Differences: NX App vs NX Library
📱 Angular Application Structure
// apps/my-app/project.json
{
"name": "my-app",
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/my-app/src",
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/apps/my-app",
"index": "apps/my-app/src/index.html", // ← App entry point
"browser": "apps/my-app/src/main.ts", // ← Bootstrap file
"polyfills": ["apps/my-app/src/polyfills.ts"],
"styles": ["apps/my-app/src/styles.scss"] // ← Global styles
}
},
"serve": { // ← Can run standalone
"executor": "@angular-devkit/build-angular:dev-server",
"options": { "port": 4200 }
}
}
}
App-Specific Files:
src/index.html- HTML entry pointsrc/main.ts- Angular bootstrapsrc/polyfills.ts- Polyfills configurationsrc/styles.scss- Global application stylessrc/app/- Application code
📚 Angular Library Structure
// libs/my-lib/project.json
{
"name": "my-lib",
"projectType": "library",
"prefix": "lib",
"sourceRoot": "libs/my-lib/src",
"targets": {
"build": {
"executor": "@nx/angular:ng-packagr-lite", // ← Library packager
"options": {
"project": "libs/my-lib/ng-package.json" // ← Uses ng-package.json
}
}
// ← NO serve, extract-i18n, or serve-static targets
}
}
Library-Specific Files:
ng-package.json- Package configurationsrc/index.ts- Public exports (library entry point)src/lib/- Library code (no app-specific files)tsconfig.lib.json- Library build config
🚀 Automated Conversion Process
📋 Prerequisites
- Backup your work: Ensure git commits are up to date
- Active NX workspace: Working NX monorepo
- Library destination: Know where the library will be created
- Dependencies: Understand the app's external dependencies
🔧 Automated Conversion Script
We've created an automated script that handles the entire conversion:
# Convert any app to library
./convert-app-to-lib.sh <app-name> <lib-name> <prefix>
# Examples:
./convert-app-to-lib.sh singularity singularity-lib logic
./convert-app-to-lib.sh ux-test ux-test-lib ux
./convert-app-to-lib.sh editor-web editor-web-lib editor
./convert-app-to-lib.sh space-map space-map-lib space
What the script does:
- ✅ Creates library structure if needed
- ✅ Moves files preserving git history
- ✅ Generates all required library configuration files
- ✅ Updates import paths automatically
- ✅ Cleans up app-specific files
- ✅ Provides next-step instructions
📂 Manual Conversion Steps
If you prefer manual control or need to understand the process:
Step 1: Generate Library Structure
# Create the library
nx generate @nx/angular:library my-lib-name \
--buildable=true \
--routing=true \
--standalone=true \
--prefix=lib
Step 2: Move Application Files
Using VSCode for best results (preserves refactoring):
# Method 1: VSCode (Recommended)
# Right-click: apps/my-app/src/app → Cut
# Right-click: libs/my-lib/src → Paste → Rename to 'lib'
# Method 2: Command Line
git mv apps/my-app/src/app/* libs/my-lib/src/lib/
Step 3: Create Library Configuration Files
ng-package.json (Required)
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/my-lib",
"lib": {
"entryFile": "src/index.ts"
}
}
src/index.ts (Required - Library Entry Point)
/*
* Public API Surface of my-lib
*/
// Export main routes
export * from './lib/my-app.routes';
// Export main components
export * from './lib/app.component';
export * from './lib/components/feature/feature.component';
// Export services
export * from './lib/services/my-app.service';
export * from './lib/state/my-app.state';
// Export models/interfaces
export * from './lib/models';
tsconfig.lib.json (Required)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test.ts"
]
}
tsconfig.lib.prod.json (Required)
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}
Step 4: Update project.json
Transform the app configuration to library configuration:
{
"name": "my-lib",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/my-lib/src",
"prefix": "lib",
"projectType": "library",
"tags": ["scope:feature", "type:library"],
"targets": {
"build": {
"executor": "@nx/angular:ng-packagr-lite",
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
"options": {
"project": "libs/my-lib/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/my-lib/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "libs/my-lib/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/my-lib/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}
Step 5: Clean Up App-Specific Files
Remove these files from the library:
rm libs/my-lib/src/lib/main.ts
rm libs/my-lib/src/lib/polyfills.ts
rm libs/my-lib/src/lib/index.html
rm libs/my-lib/src/lib/styles.scss # Move styles to component level
Step 6: Update Import Paths
Convert relative imports to library imports:
# Automated import updates
find libs/my-lib -name "*.ts" -type f -exec sed -i.bak \
-e 's|../../../libs/|@|g' \
-e 's|../../libs/|@|g' \
-e 's|../libs/|@|g' \
-e 's|libs/|@|g' \
{} \;
# Clean up backup files
find libs/my-lib -name "*.bak" -delete
Step 7: Update TypeScript Paths
Add library paths to tsconfig.base.json:
{
"compilerOptions": {
"paths": {
"@my-lib": ["libs/my-lib/src/index.ts"],
"@my-lib/*": ["libs/my-lib/src/*"]
}
}
}
Step 8: Create Routes File
Convert app routing to library routing:
// libs/my-lib/src/lib/my-app.routes.ts
import { Routes } from '@angular/router';
export const MyAppRoutes: Routes = [
{
path: '',
loadComponent: () => import('./components/layout/layout.component').then(m => m.LayoutComponent),
children: [
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
loadComponent: () => import('./components/dashboard/dashboard.component').then(m => m.DashboardComponent),
title: 'Dashboard'
},
{
path: 'feature',
loadComponent: () => import('./components/feature/feature.component').then(m => m.FeatureComponent),
title: 'Feature'
}
]
}
];
🔧 Integration with Main Application
Step 1: Import in Main App
// apps/main-app/src/app/app.routes.ts
export const routes: Routes = [
{
path: 'my-feature',
loadChildren: () => import('@my-lib').then(m => m.MyAppRoutes),
title: 'My Feature'
}
];
Step 2: Update App Configuration
// apps/main-app/src/app/app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// ... other providers
]
};
Step 3: Navigation Integration
// Main app navigation
const navigationItems = [
{
path: '/my-feature',
label: 'My Feature',
icon: 'fas fa-rocket'
}
];
✅ Validation and Testing
Step 1: Build the Library
# Build the converted library
nx build my-lib
# Build with dependencies
nx build my-lib --with-deps
Step 2: Test the Library
# Run library tests
nx test my-lib
# Run linting
nx lint my-lib
Step 3: Test Integration
# Build main app that uses the library
nx build main-app
# Serve and test navigation
nx serve main-app
Step 4: Verify Functionality
- ✅ All routes accessible
- ✅ Components render correctly
- ✅ Services function properly
- ✅ State management works
- ✅ Styles applied correctly
🚨 Common Issues and Solutions
Import Resolution Errors
Problem: Cannot find module '@my-lib'
Solution:
- Check
tsconfig.base.jsonpaths are correct - Verify
src/index.tsexports the required items - Rebuild the library:
nx build my-lib
Circular Dependency Errors
Problem: Circular dependency detected
Solution:
- Move shared types to core libraries
- Avoid importing from peer libraries
- Use interfaces instead of classes for types
Style Loading Issues
Problem: Styles not applying in library components
Solution:
- Move global styles to component-level styles
- Configure
stylePreprocessorOptionsinproject.json - Use CSS variables for theming
Route Resolution Errors
Problem: Routes not loading properly
Solution:
- Verify route exports in
src/index.ts - Check lazy loading syntax
- Ensure components are properly exported
🎯 Best Practices
1. Follow Library Naming Conventions
- Use descriptive, feature-based names
- Follow kebab-case:
my-feature-lib - Use consistent prefixes:
ux-,editor-, etc.
2. Maintain Clean Public API
- Only export what consumers need
- Group related exports
- Use barrel exports (index.ts files)
3. Handle Dependencies Properly
- Declare peer dependencies correctly
- Avoid circular dependencies
- Follow dependency hierarchy
4. Document the Migration
- Update README files
- Document breaking changes
- Provide migration examples
5. Test Thoroughly
- Test library build
- Test integration with main app
- Verify all functionality works
📋 Conversion Checklist
Pre-Conversion
- Git commits are up to date
- App dependencies documented
- Target library structure planned
- Backup strategy in place
During Conversion
- Library structure created
- Files moved preserving git history
- Configuration files created
- Import paths updated
- App-specific files removed
Post-Conversion
- Library builds successfully
- Tests pass
- Integration with main app works
- All routes accessible
- Styles applied correctly
- Documentation updated
Cleanup
- Original app directory removed (when confident)
- Package.json scripts updated
- Build configurations updated
- Team notified of changes
🔗 Related Documentation
- Adding New Libraries - Creating libraries from scratch
- Library Dependencies - Managing dependencies
- Imports & Exports - Import resolution issues
- GoGrab Space Migration - Complete migration guides
This conversion process enables you to transform standalone applications into reusable libraries that can be integrated into unified application architectures, improving code organization and reusability across your monorepo.