Mastering Component Libraries in Angular: Building Reusable and Scalable UI Components
Angular’s component-based architecture is a cornerstone of its power, enabling developers to create modular, reusable, and maintainable applications. A component library takes this modularity to the next level by packaging reusable UI components into a standalone, shareable package that can be used across multiple projects or even published for the broader community. Whether you’re building a design system for your organization or creating a public library like Angular Material, component libraries streamline development, enforce consistency, and accelerate UI creation.
In this blog, we’ll dive deep into creating component libraries in Angular, exploring their purpose, implementation, and practical applications. We’ll provide detailed explanations, step-by-step examples, and best practices to ensure you can build robust, reusable libraries. This guide is designed for developers at all levels, from those new to Angular libraries to advanced practitioners creating enterprise-grade design systems. Aligned with Angular’s latest practices as of June 2025, this content is optimized for clarity, depth, and practical utility.
What is an Angular Component Library?
An Angular component library is a collection of reusable UI components, directives, pipes, and services packaged as an Angular library. These libraries are typically distributed via npm, allowing them to be imported into Angular projects as dependencies. Each component in the library is self-contained, with its own template, styles, and logic, and can be customized through inputs, outputs, or services.
Why Create a Component Library?
Component libraries offer several advantages:
- Reusability: Share components across multiple projects, reducing duplication and development time.
- Consistency: Enforce a unified design system, ensuring consistent UI/UX across applications.
- Maintainability: Centralize updates and bug fixes, applying them to all projects using the library.
- Scalability: Support large teams and projects by providing a standardized set of building blocks.
- Community Contribution: Publish libraries to npm for public use, fostering collaboration and adoption.
When to Create a Component Library?
Create a component library when:
- You’re working on multiple Angular projects with shared UI components.
- Your organization needs a design system to ensure consistent branding and functionality.
- You want to contribute reusable components to the Angular community.
- You’re building a product where UI components need to be versioned and maintained separately.
For simpler reuse within a single project, consider Creating Reusable Components. For advanced UI integrations, see Using Angular Material for UI.
How Component Libraries Work
An Angular component library is built as an Angular library project, typically within an Angular workspace. The Angular CLI provides tools to generate, build, and package libraries, which are then published to npm or a private registry. Key concepts include:
- Angular Library: A project type in an Angular workspace, containing components, modules, and other Angular artifacts.
- NgModule: A module that declares and exports the library’s components, directives, and pipes for use in consuming applications.
- Package.json: Defines the library’s metadata, dependencies, and entry points for distribution.
- Build and Publish: The Angular CLI compiles the library into a distributable format (e.g., CommonJS, ES modules) and packages it for npm.
- Consumption: Applications import the library’s module and use its components in their templates.
Let’s walk through creating a component library step-by-step.
Creating an Angular Component Library: A Step-by-Step Guide
To demonstrate building a component library, we’ll create a simple UI library called my-ui-lib with a reusable button component and a card component. The library will support customization via inputs and events, and we’ll publish it to a local npm registry for testing.
Step 1: Set Up the Angular Workspace
Create a new Angular workspace using the Angular CLI:
ng new my-ui-workspace --create-application=false
cd my-ui-workspace
The --create-application=false flag creates a workspace without a default application, ideal for library-focused projects.
Step 2: Generate the Library
Generate a new Angular library within the workspace:
ng generate library my-ui-lib
This creates a projects/my-ui-lib directory with the following structure:
projects/my-ui-lib/
├── src/
│ ├── lib/
│ │ ├── my-ui-lib.component.ts
│ │ ├── my-ui-lib.module.ts
│ │ ├── my-ui-lib.service.ts
│ ├── public-api.ts
├── ng-package.json
├── package.json
├── tsconfig.lib.json
Explanation:
- lib/: Contains the library’s source code, including components, modules, and services.
- public-api.ts: Defines the library’s public API, exporting components, modules, and other artifacts for consumers.
- ng-package.json: Configures the library’s build process, specifying entry points and output formats.
- package.json: Defines the library’s metadata and dependencies.
Step 3: Create Components
Let’s create two components: a customizable button and a card component.
Generate the Button Component
ng generate component button --project=my-ui-lib
Update projects/my-ui-lib/src/lib/button/button.component.ts:
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'my-ui-button',
templateUrl: './button.component.html',
styleUrls: ['./button.component.css']
})
export class ButtonComponent {
@Input() label: string = 'Click Me';
@Input() type: 'primary' | 'secondary' | 'danger' = 'primary';
@Input() disabled: boolean = false;
@Output() buttonClick = new EventEmitter();
onClick(): void {
if (!this.disabled) {
this.buttonClick.emit();
}
}
}
Update button.component.html:
{ { label }}
Update button.component.css:
.my-ui-button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.primary {
background-color: #007bff;
color: white;
}
.secondary {
background-color: #6c757d;
color: white;
}
.danger {
background-color: #dc3545;
color: white;
}
.my-ui-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
Explanation:
- The button component accepts label, type, and disabled inputs for customization.
- It emits a buttonClick event when clicked, respecting the disabled state.
- Styles are scoped to the component, with classes for different button types.
Generate the Card Component
ng generate component card --project=my-ui-lib
Update projects/my-ui-lib/src/lib/card/card.component.ts:
import { Component, Input } from '@angular/core';
@Component({
selector: 'my-ui-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent {
@Input() title: string = '';
@Input() showBorder: boolean = true;
}
Update card.component.html:
{ { title }}
Update card.component.css:
.my-ui-card {
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}
.bordered {
border: 1px solid #ddd;
}
h3 {
margin-top: 0;
color: #333;
}
Explanation:
- The card component accepts a title and showBorder inputs.
- It uses for content projection, allowing consumers to inject custom content.
- Styles provide a clean card layout with an optional border.
For more on content projection, see Using Content Projection.
Step 4: Export Components
Update the library’s public API (projects/my-ui-lib/src/public-api.ts) to export the components and module:
export * from './lib/my-ui-lib.module';
export * from './lib/button/button.component';
export * from './lib/card/card.component';
Update the library module (projects/my-ui-lib/src/lib/my-ui-lib.module.ts) to declare and export the components:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button/button.component';
import { CardComponent } from './card/card.component';
@NgModule({
declarations: [ButtonComponent, CardComponent],
imports: [CommonModule],
exports: [ButtonComponent, CardComponent]
})
export class MyUiLibModule {}
Explanation:
- The MyUiLibModule declares and exports the components, making them available to consuming applications.
- The CommonModule is imported for directives like *ngIf and [ngClass].
- The public-api.ts file ensures consumers can import the module and components directly.
Step 5: Build the Library
Build the library to generate distributable files:
ng build my-ui-lib
This creates a dist/my-ui-lib directory with compiled JavaScript, TypeScript declarations, and metadata files.
Explanation:
- The ng build command compiles the library into formats like CommonJS and ES modules.
- The output is ready for packaging and distribution via npm.
Step 6: Test the Library Locally
To test the library before publishing, create a sample application in the workspace:
ng generate application my-ui-app
Link the library locally for testing:
cd dist/my-ui-lib
npm link
cd ../..
cd projects/my-ui-app
npm link my-ui-lib
Update projects/my-ui-app/src/app/app.module.ts to import the library:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MyUiLibModule } from 'my-ui-lib';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, MyUiLibModule],
bootstrap: [AppComponent]
})
export class AppModule {}
Update projects/my-ui-app/src/app/app.component.html to use the components:
My UI Library Demo
This is a sample card content.
Update projects/my-ui-app/src/app/app.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
onButtonClick(): void {
console.log('Button clicked!');
}
}
Update projects/my-ui-app/src/app/app.component.css:
.app-container {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
h1 {
text-align: center;
}
Run the application:
ng serve my-ui-app
Open your browser to http://localhost:4200. You’ll see a card with a title, containing a primary button (clickable) and a disabled secondary button. Clicking the primary button logs a message to the console, demonstrating the library’s functionality.
Explanation:
- The npm link command creates a symbolic link to the library, allowing the app to use it as a dependency.
- The app imports MyUiLibModule and uses the my-ui-button and my-ui-card components.
- The components behave as expected, with inputs, outputs, and styles applied correctly.
Step 7: Publish the Library (Optional)
To publish the library to npm (or a private registry), update projects/my-ui-lib/package.json:
{
"name": "@my-org/my-ui-lib",
"version": "1.0.0",
"peerDependencies": {
"@angular/common": "^17.0.0",
"@angular/core": "^17.0.0"
},
"dependencies": {},
"sideEffects": false
}
Build and publish:
ng build my-ui-lib
cd dist/my-ui-lib
npm publish
Explanation:
- Update the name to a scoped package (e.g., @my-org/my-ui-lib) for npm.
- Set peerDependencies to ensure compatibility with Angular’s version.
- The npm publish command uploads the library to npm, making it available for installation (e.g., npm install @my-org/my-ui-lib).
Note: For public publishing, ensure you have an npm account and are logged in (npm login). For private registries, configure .npmrc with your registry details.
Advanced Component Library Features
Component libraries can include advanced features to enhance functionality and usability. Let’s explore two advanced scenarios.
1. Theming Support
To support customizable styling, add theming capabilities using CSS custom properties or a theme service.
Add Theme Support to Button
Update button.component.css:
.my-ui-button {
padding: 10px 20px;
border: none;
border-radius: var(--button-border-radius, 4px);
cursor: pointer;
font-size: var(--button-font-size, 16px);
background-color: var(--button-bg-color, #007bff);
color: var(--button-text-color, white);
}
.primary {
--button-bg-color: var(--primary-bg-color, #007bff);
--button-text-color: var(--primary-text-color, white);
}
.secondary {
--button-bg-color: var(--secondary-bg-color, #6c757d);
--button-text-color: var(--secondary-text-color, white);
}
.danger {
--button-bg-color: var(--danger-bg-color, #dc3545);
--button-text-color: var(--danger-text-color, white);
}
.my-ui-button:disabled {
--button-bg-color: var(--disabled-bg-color, #cccccc);
cursor: not-allowed;
}
Provide Theme Customization
Create a theme service in the library:
ng generate service theme --project=my-ui-lib
Update projects/my-ui-lib/src/lib/theme.service.ts:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ThemeService {
setTheme(theme: Partial<{
primaryBgColor: string;
primaryTextColor: string;
secondaryBgColor: string;
secondaryTextColor: string;
dangerBgColor: string;
dangerTextColor: string;
buttonBorderRadius: string;
buttonFontSize: string;
}>): void {
const root = document.documentElement;
Object.entries(theme).forEach(([key, value]) => {
root.style.setProperty(`--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`, value);
});
}
}
Export the service in public-api.ts:
export * from './lib/theme.service';
Use Theme in the App
Update projects/my-ui-app/src/app/app.component.ts:
import { Component, OnInit } from '@angular/core';
import { ThemeService } from 'my-ui-lib';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor(private themeService: ThemeService) {}
ngOnInit(): void {
this.themeService.setTheme({
primaryBgColor: '#28a745',
primaryTextColor: '#ffffff',
buttonBorderRadius: '8px'
});
}
onButtonClick(): void {
console.log('Button clicked!');
}
}
Explanation:
- CSS custom properties (e.g., --button-bg-color) allow consumers to override styles.
- The ThemeService sets custom properties on the :root element, enabling global theme changes.
- The app customizes the primary button’s background and border radius, demonstrating theming flexibility.
For more on theming, see Creating Custom Themes.
2. Lazy-Loadable Library Module
To optimize application performance, make the library module lazy-loadable by providing a forRoot method.
Update projects/my-ui-lib/src/lib/my-ui-lib.module.ts:
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button/button.component';
import { CardComponent } from './card/card.component';
import { ThemeService } from './theme.service';
@NgModule({
declarations: [ButtonComponent, CardComponent],
imports: [CommonModule],
exports: [ButtonComponent, CardComponent]
})
export class MyUiLibModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MyUiLibModule,
providers: [ThemeService]
};
}
}
Update the app’s module to use forRoot:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MyUiLibModule } from 'my-ui-lib';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, MyUiLibModule.forRoot()],
bootstrap: [AppComponent]
})
export class AppModule {}
Explanation:
- The forRoot method ensures the ThemeService is provided at the root level, preventing duplicate instances.
- This pattern supports lazy loading by allowing the library to be imported in feature modules without re-providing services.
For more on lazy loading, see Angular Lazy Loading.
Best Practices for Component Libraries
To create effective component libraries, follow these best practices: 1. Encapsulate Logic: Keep components self-contained, using inputs and outputs for communication. See Angular Emit Event from Child to Parent Component. 2. Support Customization: Provide inputs, content projection, and theming options to make components flexible. 3. Use ViewEncapsulation: Default to Emulated encapsulation for style isolation, but consider None for theming. See Using View Encapsulation. 4. Document Components: Include a README, API documentation, and examples in the library’s repository. 5. Test Thoroughly: Write unit tests for components and services, and test in a sample app. See Testing Components with Jasmine. 6. Version Carefully: Follow semantic versioning (e.g., 1.0.0) and document breaking changes in release notes. 7. Optimize Build: Use ng-package.json to exclude unnecessary files and enable tree-shaking. See Using Tree Shaking in Build. 8. Ensure Accessibility: Follow a11y guidelines, using ARIA attributes and keyboard navigation. See Implementing A11y in App.
Debugging Component Libraries
If the library isn’t working as expected, try these troubleshooting steps:
- Check Public API: Ensure components and modules are exported in public-api.ts.
- Verify Build Output: Inspect dist/my-ui-lib to confirm compiled files are correct.
- Test Linking: Confirm npm link worked by checking the app’s node_modules/my-ui-lib.
- Inspect Module Imports: Ensure MyUiLibModule is imported correctly in the consuming app.
- Debug Component Bindings: Log inputs and events to verify component behavior.
- Review Peer Dependencies: Ensure the app’s Angular version matches the library’s peerDependencies.
FAQ
What’s the difference between a component library and a regular Angular module?
A component library is an Angular library project, packaged for distribution (e.g., via npm), with a public API and reusable components. A regular module is part of an application, not designed for external reuse.
Can I use Angular Material components in my library?
Yes, but include @angular/material as a peer dependency in package.json and import its modules in your library’s NgModule. See Using Angular Material for UI.
How do I update a published library?
Increment the version in package.json (e.g., 1.0.1), rebuild (ng build my-ui-lib), and republish (npm publish). Document changes in a changelog.
Can I create a library without the Angular CLI?
Yes, but the CLI simplifies setup, build, and packaging. Manual creation requires configuring ng-package.json, tsconfig, and build scripts.
Conclusion
Creating an Angular component library empowers developers to build reusable, consistent, and scalable UI components that can be shared across projects or the community. By leveraging the Angular CLI, NgModule, and best practices, you can create libraries that support customization, theming, and lazy loading, as demonstrated in our my-ui-lib example. From simple buttons to complex cards, component libraries streamline development and enforce design systems.
This guide has provided a comprehensive exploration of building component libraries, complete with practical examples, advanced features, and best practices. To further enhance your Angular skills, explore related topics like Using Angular Elements, Creating Custom Directives, or Optimizing Build for Production.