Reusable components are the foundation of scalable Angular applications. They allow developers to write code once and use it multiple times across different features, pages, or modules. This reduces duplication, improves consistency, and makes the application easier to maintain as it grows.
Angular’s component-based architecture is designed with reusability in mind. However, creating components that are truly reusable requires planning, modular design, and adherence to certain best practices.
This article walks you through how to design, build, and manage reusable components in Angular with real examples and strategies used in production-grade applications.
1. What Makes a Component Reusable
A reusable component is:
Reusable components focus on presentation, not business logic.
Examples
Buttons
Input fields
Dropdowns
Data tables
Cards
Modals
Charts
Reusable layouts
These components become building blocks of your application.
2. Why Reusable Components Matter
Reusable components help developers:
Reduce code duplication
Maintain a consistent UI
Increase productivity
Improve maintainability
Scale applications faster
Reduce bugs through standard components
For large teams, reusable components create a unified UI language.
3. Setting Up a Shared Module for Reusable Components
To store reusable components, create a shared module:
ng generate module shared
Folder structure:
src/app/shared/
components/
button/
card/
modal/
pipes/
directives/
shared.module.ts
Export reusable components through the shared module:
@NgModule({
declarations: [
ButtonComponent,
CardComponent,
ModalComponent
],
exports: [
ButtonComponent,
CardComponent,
ModalComponent
],
imports: [
CommonModule
]
})
export class SharedModule {}
Import SharedModule into other modules to use the components.
4. Building a Reusable Button Component
Create a reusable button:
ng generate component shared/components/button
button.component.ts:
@Component({
selector: 'app-button',
templateUrl: './button.component.html'
})
export class ButtonComponent {
@Input() label!: string;
@Input() type: 'primary' | 'secondary' = 'primary';
@Input() disabled = false;
@Output() clicked = new EventEmitter<void>();
onClick() {
if (!this.disabled) {
this.clicked.emit();
}
}
}
button.component.html:
<button
[class.primary]="type === 'primary'"
[class.secondary]="type === 'secondary'"
[disabled]="disabled"
(click)="onClick()"
>
{{ label }}
</button>
Now you can use it anywhere:
<app-button
label="Save"
type="primary"
(clicked)="onSave()">
</app-button>
5. Building a Reusable Input Component
Create the input component:
ng generate component shared/components/input
input.component.ts:
@Component({
selector: 'app-input',
templateUrl: './input.component.html'
})
export class InputComponent {
@Input() label!: string;
@Input() placeholder = '';
@Input() value = '';
@Output() valueChange = new EventEmitter<string>();
onInput(event: any) {
this.valueChange.emit(event.target.value);
}
}
input.component.html:
<label>{{ label }}</label>
<input
[placeholder]="placeholder"
[value]="value"
(input)="onInput($event)"
/>
Use it anywhere:
<app-input
label="Username"
[(value)]="username">
</app-input>
6. Creating a Reusable Modal Component
ng generate component shared/components/modal
modal.component.ts:
@Component({
selector: 'app-modal',
templateUrl: './modal.component.html'
})
export class ModalComponent {
@Input() title!: string;
@Input() visible = false;
@Output() closed = new EventEmitter<void>();
close() {
this.visible = false;
this.closed.emit();
}
}
modal.component.html:
<div *ngIf="visible" class="modal-overlay">
<div class="modal-box">
<h2>{{ title }}</h2>
<ng-content></ng-content>
<button (click)="close()">Close</button>
</div>
</div>
The modal supports content projection.
Use example:
<app-modal
title="Edit Profile"
[visible]="showModal"
(closed)="showModal = false">
<p>Modal body content goes here</p>
</app-modal>
7. Designing Configurable Components Using Inputs
Reusable components should accept configuration properties using @Input().
Example configuration for a table:
@Input() columns: string[] = [];
@Input() rows: any[] = [];
@Input() bordered = false;
@Input() striped = false;
This allows the component to adapt to different needs.
8. Emitting Custom Events Using Outputs
Output events make components interactive.
Example event emitter:
@Output() selected = new EventEmitter<any>();
onRowClick(row: any) {
this.selected.emit(row);
}
Usage:
<app-data-table
[rows]="users"
(selected)="onUserSelected($event)">
</app-data-table>
9. Using Content Projection for Reusable Layouts
Angular’s <ng-content> allows embedding custom HTML inside a reusable component.
Example: A reusable card layout.
card.component.html:
<div class="card">
<div class="card-header">
<ng-content select="[card-title]"></ng-content>
</div>
<div class="card-body">
<ng-content select="[card-body]"></ng-content>
</div>
</div>
Usage:
<app-card>
<div card-title>User Details</div>
<div card-body>Some description here</div>
</app-card>
This enables flexible, dynamic layouts.
10. Reusing Components with Dynamic Data
A good reusable component is not tied to fixed data structures.
Example for a dynamic dropdown:
@Input() options: { label: string, value: any }[] = [];
Now it can support:
Countries
Cities
Product categories
User roles
Any dataset fits this structure.
11. Building Reusable Components with Dependency Injection
Reusable components often depend on services.
Example
A reusable toast notification component using a service:
toast.service.ts:
@Injectable({
providedIn: 'root',
})
export class ToastService {
toast$ = new Subject<string>();
show(message: string) {
this.toast$.next(message);
}
}
toast.component.ts:
@Component({
selector: 'app-toast',
templateUrl: './toast.component.html'
})
export class ToastComponent {
message = '';
constructor(private toastService: ToastService) {
this.toastService.toast$.subscribe(msg => {
this.message = msg;
});
}
}
Reusable across the whole app.
12. Reusable Component Patterns
Pattern 1: Input + Output
For small interactive components.
Pattern 2: Content Projection
For flexible layouts.
Pattern 3: ControlValueAccessor
For reusable form components.
Example: Custom input field integrated with Angular forms.
Use NG_VALUE_ACCESSOR to make custom components behave as regular form controls.
Pattern 4: Dynamic Components
Useful for dashboards, modals, widget systems.
Pattern 5: Smart vs Dumb Components
Reusable components should be dumb components, receiving data and emitting events only.
13. Reusable Form Inputs Using ControlValueAccessor
ControlValueAccessor allows custom components to be used with Angular forms.
Example
@Component({
selector: 'app-textbox',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TextboxComponent),
multi: true
}]
})
export class TextboxComponent implements ControlValueAccessor {
value = '';
onChange = (_: any) => {};
onTouched = () => {};
writeValue(val: any): void {
this.value = val;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
}
This enables:
<form>
<app-textbox formControlName="name"></app-textbox>
</form>
14. Best Practices for Reusable Components
Keep components small and specific
Avoid business logic inside reusable components
Use Input() for flexibility
Use Output() for communication
Use content projection for layouts
Separate reusable components into a shared module
Avoid coupling components with API calls
Test components individually
Use consistent naming conventions
Document component usage for your team
Conclusion
Reusable components are essential for building scalable and maintainable Angular applications. By leveraging Angular’s component architecture, content projection, input-output bindings, and patterns such as ControlValueAccessor and smart-dumb components, developers can create flexible and powerful building blocks for UI development.
With proper structure, a shared module, and careful design decisions, your Angular application becomes more modular, robust, and easier to extend. Reusable components save time, reduce bugs, and help teams deliver high-quality applications faster.