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:

📚 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:


🚀 Automated Conversion Process

📋 Prerequisites

  1. Backup your work: Ensure git commits are up to date
  2. Active NX workspace: Working NX monorepo
  3. Library destination: Know where the library will be created
  4. 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:

  1. ✅ Creates library structure if needed
  2. ✅ Moves files preserving git history
  3. ✅ Generates all required library configuration files
  4. ✅ Updates import paths automatically
  5. ✅ Cleans up app-specific files
  6. ✅ 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"
  }
}

index.ts (Required)

/*
 * Index 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


🚨 Common Issues and Solutions

Import Resolution Errors

Problem: Cannot find module '@my-lib'

Solution:

  1. Check tsconfig.base.json paths are correct
  2. Verify index.ts exports the required items
  3. Rebuild the library: nx build my-lib

Circular Dependency Errors

Problem: Circular dependency detected

Solution:

  1. Move shared types to core libraries
  2. Avoid importing from peer libraries
  3. Use interfaces instead of classes for types

Style Loading Issues

Problem: Styles not applying in library components

Solution:

  1. Move global styles to component-level styles
  2. Configure stylePreprocessorOptions in project.json
  3. Use CSS variables for theming

Route Resolution Errors

Problem: Routes not loading properly

Solution:

  1. Verify route exports in index.ts
  2. Check lazy loading syntax
  3. Ensure components are properly exported

🎯 Best Practices

1. Follow Library Naming Conventions

2. Maintain Clean Public API

3. Handle Dependencies Properly

4. Document the Migration

5. Test Thoroughly


📋 Conversion Checklist

Pre-Conversion

During Conversion

Post-Conversion

Cleanup


🔗 Related Documentation


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.