How to Build a Custom Component Library with Angular + Storybook

Date: September 22, 2024

In modern Angular development, creating reusable component libraries is key to maintaining consistency and speeding up development across multiple projects. But how do you build, document, and publish such a library efficiently?

This tutorial walks you through creating a custom Angular component library from scratch, integrating Storybook for interactive documentation, and preparing the library for publishing and reuse.


Why Build a Component Library?

  • Reusability: Share UI components across projects and teams.
  • Consistency: Ensure consistent styling and behavior.
  • Maintainability: Centralize updates and fixes.
  • Documentation: Provide clear, interactive usage docs via Storybook.

Step 1: Setting Up Your Angular Workspace

Start with an Nx or Angular CLI workspace:

ng new angular-component-library --create-application=false
cd angular-component-library

Create a new Angular library:

ng generate library ui-components

This creates a library project under projects/ui-components with boilerplate.


Step 2: Building Your First Component

Navigate to your library folder:

cd projects/ui-components/src/lib

Create a simple reusable button component:

// button.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'ui-button',
  template: `
    <button [ngClass]="type" [disabled]="disabled">
      <ng-content></ng-content>
    </button>
  `,
  styles: [
    `
      button {
        font-size: 1rem;
        padding: 0.5rem 1rem;
        border-radius: 4px;
        border: none;
        cursor: pointer;
      }
      .primary {
        background-color: #007bff;
        color: white;
      }
      .secondary {
        background-color: #6c757d;
        color: white;
      }
      button:disabled {
        background-color: #ccc;
        cursor: not-allowed;
      }
    `,
  ],
})
export class ButtonComponent {
  @Input() type: 'primary' | 'secondary' = 'primary';
  @Input() disabled = false;
}

Export this component in the ui-components.module.ts:

@NgModule({
  declarations: [ButtonComponent],
  exports: [ButtonComponent],
})
export class UiComponentsModule {}

Step 3: Adding Storybook for Interactive Documentation

Install Storybook:

npx sb init --type angular

Configure Storybook to load your library components. In .storybook/main.js:

module.exports = {
  stories: ['../projects/ui-components/src/lib/**/*.stories.ts'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
};

Create a Storybook story for your button component:

// button.component.stories.ts
import { Meta, Story } from '@storybook/angular';
import { ButtonComponent } from './button.component';

export default {
  title: 'UI/Button',
  component: ButtonComponent,
} as Meta;

const Template: Story<ButtonComponent> = (args) => ({
  props: args,
  template: `<ui-button [type]="type" [disabled]="disabled">Button</ui-button>`,
});

export const Primary = Template.bind({});
Primary.args = {
  type: 'primary',
  disabled: false,
};

export const Disabled = Template.bind({});
Disabled.args = {
  type: 'primary',
  disabled: true,
};

export const Secondary = Template.bind({});
Secondary.args = {
  type: 'secondary',
  disabled: false,
};

Run Storybook to see your button variants live:

npm run storybook

Step 4: Testing Your Components

Use Angular testing utilities to create unit tests:

// button.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ButtonComponent } from './button.component';

describe('ButtonComponent', () => {
  let component: ButtonComponent;
  let fixture: ComponentFixture<ButtonComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ButtonComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(ButtonComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should disable button when disabled is true', () => {
    component.disabled = true;
    fixture.detectChanges();
    const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
    expect(button.disabled).toBeTrue();
  });
});

Step 5: Building and Publishing the Library

Build your Angular library:

ng build ui-components

This outputs your library in dist/ui-components.

To publish to npm:

  1. Make sure your package.json inside dist/ui-components is configured with name, version, and metadata.
  2. Login to npm:
npm login
  1. Publish your package:
cd dist/ui-components
npm publish

Step 6: Consuming Your Library in an App

In any Angular app, install your published library:

npm install your-library-name

Import the module in your app:

import { UiComponentsModule } from 'your-library-name';

@NgModule({
  imports: [UiComponentsModule],
})
export class AppModule {}

Use the button component:

<ui-button type="primary">Click Me</ui-button>

Summary

By building your own Angular component library with Storybook integration, you achieve:

  • Reusable, testable, and well-documented UI components
  • Faster development with shared design patterns
  • Professional documentation that fosters collaboration

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *