<link rel="stylesheet" href="assets/fonts/inter.css"> Proven Publishing an Angular Component Library to NPM:… | Viral Patel Studio Skip to main content

Proven Publishing an Angular Component Library to NPM: Step-by-Step Guide 2025

Dec 15, 2025By Viral Patel

Learn how to build, package, and publish an Angular component library to NPM in 2025 with ng-packagr, peer dependencies, and GitHub Actions CI/CD.

Publishing an Angular Component Library to NPM: Step-by-Step Guide 2025

Angular NPM Publishing Workflow

learn about Angular

component library

angular

design systems

Expert NX Monorepo for Angular Design Systems: Enterprise Guide 2025 Figure 1: The complete pipeline from Angular source code to a published NPM package

Why Publish Your Angular Library?

Sharing reusable UI components across projects is one of the highest-leverage investments a design system team can make. A published NPM library means one source of truth, versioned releases, and no copy-paste drift. In 2025, the Angular ecosystem makes this easier than ever — but there are still pitfalls that trip up even experienced developers.

This guide walks you through every step: generating the library scaffold, configuring the Angular Package Format, setting up semantic versioning, and automating releases with GitHub Actions.


Step 1: Generate the Library Scaffold

Start inside an existing Angular workspace (or create one with ng new my-workspace --no-create-application):

ng generate library ui-components --prefix ds

This creates:

  • projects/ui-components/src/lib/ — your component source
  • projects/ui-components/src/public-api.ts — the barrel file that controls your public API
  • projects/ui-components/ng-package.json — ng-packagr configuration
  • projects/ui-components/package.json — the library's own package.json

The Public API Barrel File

Everything you want consumers to use must be exported from public-api.ts:

// projects/ui-components/src/public-api.ts
export * from './lib/button/button.component';
export * from './lib/button/button.module';
export * from './lib/card/card.component';
export * from './lib/tokens/design-tokens';

If a symbol isn't listed here, it won't be accessible after publishing — even if TypeScript can see it during development.


Step 2: Configure ng-package.json

The ng-package.json file tells ng-packagr how to build your library:

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "lib": {
    "entryFile": "src/public-api.ts"
  },
  "dest": "../../dist/ui-components"
}

Adding Secondary Entrypoints

Secondary entrypoints let consumers import subsets of your library, improving tree-shaking:

projects/ui-components/
  src/
    public-api.ts          ← primary entrypoint
  testing/
    src/
      public-api.ts        ← secondary: @my-org/ui-components/testing
    ng-package.json
  icons/
    src/
      public-api.ts        ← secondary: @my-org/ui-components/icons
    ng-package.json

Each secondary entrypoint has its own minimal ng-package.json:

{
  "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}

Step 3: Configure the Library's package.json

This is where most publish failures originate. The library's package.json (in projects/ui-components/) needs careful attention:

{
  "name": "@my-org/ui-components",
  "version": "1.0.0",
  "peerDependencies": {
    "@angular/common": ">=17.0.0 <20.0.0",
    "@angular/core": ">=17.0.0 <20.0.0",
    "rxjs": ">=7.4.0 <8.0.0"
  },
  "sideEffects": false,
  "exports": {
    ".": {
      "import": "./fesm2022/my-org-ui-components.mjs",
      "default": "./fesm2022/my-org-ui-components.mjs"
    },
    "./testing": {
      "import": "./testing/fesm2022/my-org-ui-components-testing.mjs",
      "default": "./testing/fesm2022/my-org-ui-components-testing.mjs"
    }
  }
}

Key points:

  • peerDependencies — never list Angular as a dependency. Dual Angular instances cause runtime errors.
  • sideEffects: false — tells bundlers the library is fully tree-shakeable.
  • exports field — required for Node.js 12+ module resolution and Angular's package format.

Step 4: Build for Production

Always build before publishing:

ng build ui-components --configuration production

The output in dist/ui-components/ contains:

  • fesm2022/ — FESM2022 bundle (single flat file, optimal for modern toolchains)
  • esm2022/ — individual ESM files (used for better tree-shaking in some bundlers)
  • *.d.ts — TypeScript declarations
  • *.d.ts.map — source maps for declarations

Verify the build output looks correct before proceeding:

ls dist/ui-components/
# Should show: fesm2022/, esm2022/, package.json, README.md, *.d.ts

Step 5: Set Up Semantic Versioning

Follow semver religiously — consumers depend on it for safe upgrades:

# Bug fix: 1.0.0 → 1.0.1
npm version patch --workspace=projects/ui-components

# New backward-compatible feature: 1.0.1 → 1.1.0
npm version minor --workspace=projects/ui-components

# Breaking change: 1.1.0 → 2.0.0
npm version major --workspace=projects/ui-components

Document breaking changes in a CHANGELOG.md using Keep a Changelog format.


Step 6: Create .npmignore

Prevent development files from bloating the published package:

# .npmignore (in projects/ui-components/)
*.spec.ts
*.spec.js
karma.conf.js
tsconfig.spec.json
src/
!dist/
jest.config.ts

Step 7: Publish to NPM

First, do a dry run to catch issues:

cd dist/ui-components
npm publish --dry-run --access public

Review the output — it shows exactly what files will be published and their sizes. Once satisfied:

npm publish --access public

For scoped packages under an organization, --access public is required or it defaults to private (which requires a paid NPM plan).


Step 8: Automate with GitHub Actions

Manual publishing is error-prone. Automate it with a tag-triggered workflow:

# .github/workflows/publish-library.yml
name: Publish Angular Library to NPM

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
          registry-url: 'https://registry.npmjs.org'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: ng test ui-components --watch=false --browsers=ChromeHeadless

      - name: Build library
        run: ng build ui-components --configuration production

      - name: Publish to NPM
        run: npm publish --access public
        working-directory: dist/ui-components
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

To trigger a release:

npm version minor --workspace=projects/ui-components
git push --follow-tags

The --follow-tags flag pushes both the commit and the tag, triggering the workflow.


Pre-Publish Checklist

Check Why It Matters
All exports in public-api.ts Missing exports break consumer imports silently
Angular peer dependency range is broad Prevents forcing consumers to upgrade Angular
sideEffects: false set Enables tree-shaking in consumer apps
exports field in package.json Required for sub-path imports and modern resolution
Dry-run passes with expected files Catches .npmignore misconfigurations
Version number bumped NPM rejects publishing the same version twice
CHANGELOG updated Consumers need to understand what changed
Tests pass on CI Prevents publishing broken code

Common Pitfalls

Pitfall Symptom Fix
Publishing from workspace root Wrong files published cd dist/ui-components && npm publish
Angular as dependency Duplicate Angular instances at runtime Move to peerDependencies
Missing public-api.ts exports Cannot find module errors in consumers Audit and add all public symbols
Missing exports field Sub-path imports fail with ERR_PACKAGE_PATH_NOT_EXPORTED Add exports map to package.json
No dry-run before publish Hidden files or missing files published Always npm publish --dry-run first
Not bumping version npm publish fails with 403 npm version patch/minor/major
Global styles not in CSS bundle Consumer styles missing Add styleUrls and configure ng-packagr to process SCSS
No --access public on scoped package Defaults to private, fails without paid plan Always pass --access public for OSS

Consuming Your Published Library

Once published, consumers install and use it like any Angular package:

npm install @my-org/ui-components

In their app.module.ts or standalone component:

import { ButtonComponent } from '@my-org/ui-components';
import { SpinnerComponent } from '@my-org/ui-components/loading';

@Component({
  imports: [ButtonComponent, SpinnerComponent],
  // ...
})
export class AppComponent {}

What's Next?

Publishing is just the start. To build a world-class component library ecosystem:

A well-published library is a multiplier — every component you ship once gets reused dozens of times across products.