Angular has become a powerhouse in the world of web development, and for good reason. It’s robust, scalable, and perfect for building complex applications. But let’s face it, writing the same components over and over again can be a real drag. That’s where a reusable component library comes in handy!
Building a reusable component library in Angular isn’t just about saving time. It’s about creating a consistent look and feel across your entire application, making your codebase more maintainable, and ultimately, delivering a better user experience. So, let’s dive into how you can create your very own component library that’s flexible, powerful, and easy to use.
First things first, you’ll need to set up a new Angular project. If you haven’t already, install the Angular CLI and create a new project:
ng new my-component-library
cd my-component-library
Now, let’s create a new component that we’ll add to our library. We’ll start with something simple, like a button component:
ng generate component components/button
This will create a new button component in the src/app/components
directory. Open up the button.component.ts
file and let’s make it more flexible:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-button',
template: `
<button [ngClass]="['btn', type]" [disabled]="disabled">
<ng-content></ng-content>
</button>
`,
styles: [`
.btn {
padding: 10px 20px;
border: none;
cursor: pointer;
}
.primary {
background-color: #007bff;
color: white;
}
.secondary {
background-color: #6c757d;
color: white;
}
`]
})
export class ButtonComponent {
@Input() type: 'primary' | 'secondary' = 'primary';
@Input() disabled: boolean = false;
}
This button component is now reusable and flexible. You can use it like this:
<app-button type="primary">Click me!</app-button>
<app-button type="secondary" [disabled]="true">Can't click me!</app-button>
But creating individual components is just the beginning. To make a truly reusable library, we need to package these components together. This is where Angular libraries come in.
Let’s create a new library for our components:
ng generate library my-component-lib
This will create a new library in the projects
directory. Now, let’s move our button component into this library. Create a new components
folder in projects/my-component-lib/src/lib
and move the button component files there.
Next, we need to export our component from the library. Open projects/my-component-lib/src/public-api.ts
and add:
export * from './lib/components/button/button.component';
Now, we can build our library:
ng build my-component-lib
Great! We’ve created our first reusable component in a library. But what about styling? One of the challenges with component libraries is making them visually customizable. Enter CSS custom properties (also known as CSS variables).
Let’s update our button component to use CSS variables:
@Component({
selector: 'app-button',
template: `
<button [ngClass]="['btn', type]" [disabled]="disabled">
<ng-content></ng-content>
</button>
`,
styles: [`
.btn {
padding: var(--btn-padding, 10px 20px);
border: none;
cursor: pointer;
}
.primary {
background-color: var(--btn-primary-bg, #007bff);
color: var(--btn-primary-color, white);
}
.secondary {
background-color: var(--btn-secondary-bg, #6c757d);
color: var(--btn-secondary-color, white);
}
`]
})
export class ButtonComponent {
// ... same as before
}
Now, users of your library can easily customize the look of the buttons by setting these CSS variables in their own stylesheets.
But what about more complex components? Let’s say we want to create a reusable card component that can display different types of content. We can use Angular’s content projection for this:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header" *ngIf="title">{{ title }}</div>
<div class="card-body">
<ng-content></ng-content>
</div>
<div class="card-footer" *ngIf="footerTemplate">
<ng-container [ngTemplateOutlet]="footerTemplate"></ng-container>
</div>
</div>
`,
styles: [`
.card {
border: 1px solid #ddd;
border-radius: 4px;
}
.card-header {
background-color: #f5f5f5;
padding: 10px;
border-bottom: 1px solid #ddd;
}
.card-body {
padding: 15px;
}
.card-footer {
background-color: #f5f5f5;
padding: 10px;
border-top: 1px solid #ddd;
}
`]
})
export class CardComponent {
@Input() title?: string;
@Input() footerTemplate?: TemplateRef<any>;
}
This card component can be used like this:
<app-card title="My Card">
<p>This is the content of my card.</p>
</app-card>
<app-card [footerTemplate]="myFooter">
<h2>Another Card</h2>
<p>This one has a custom footer.</p>
</app-card>
<ng-template #myFooter>
<button>Click me!</button>
</ng-template>
As your library grows, you might want to add more advanced features. For example, you could create a theme service that allows users to switch between different pre-defined themes:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ThemeService {
private themeSubject = new BehaviorSubject<'light' | 'dark'>('light');
theme$ = this.themeSubject.asObservable();
setTheme(theme: 'light' | 'dark') {
this.themeSubject.next(theme);
}
}
You can then use this service in your components to apply different styles based on the current theme.
Another important aspect of building a reusable component library is documentation. Good documentation can make or break a library. Consider using tools like Storybook to create interactive documentation for your components.
Testing is also crucial. Make sure to write unit tests for all your components and services. Angular’s TestBed makes it easy to test components in isolation:
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();
});
beforeEach(() => {
fixture = TestBed.createComponent(ButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have primary class by default', () => {
const buttonElement: HTMLElement = fixture.nativeElement.querySelector('button');
expect(buttonElement.classList).toContain('primary');
});
// Add more tests...
});
As your library grows, you might find yourself needing to manage state across components. While Angular’s services are great for this, you might want to consider using a state management library like NgRx for more complex scenarios.
Remember, building a reusable component library is an iterative process. Start small, get feedback from your team or the community, and continuously improve. Pay attention to performance, accessibility, and browser compatibility.
And there you have it! You’re now on your way to creating a flexible, powerful, and reusable component library in Angular. Happy coding!