As web application developers, we constantly seek ways to build more robust, scalable, and maintainable applications. While there are multiple approaches to structuring a component, I want to make a case for the composable component pattern, which offers significant advantages in many scenarios.
Note: The examples in this article are for angular, but this pattern can be used in any framework.
The Tale of Joe and the Evolving Form: A Lesson in Component Design
Joe found himself adding even more conditional logic:
@Component({
selector: 'techcorp-form',
template: `
<form (ngSubmit)="onSubmit()">
@for (field of formFields; track field.name) {
<div>
<label>{{ field.label }}</label>
<input
[type]="field.type"
[name]="field.name"
[placeholder]="field.placeholder"
[(ngModel)]="formData[field.name]"
>
@if (field.name === 'email' && validateEmailDomain) {
<span>* Email domain will be validated</span>
}
</div>
}
<button type="submit">Submit</button>
</form>
`
})
export class FormComponent {
@Input() formFields!: Array<{ label: string; type: string; name: string; placeholder?: string }>;
@Input() validateEmailDomain = false;
@Input() includeSource = false;
formData: Record<string, string> = {};
onSubmit() {
if (this.validateEmailDomain) {
// Perform email domain validation
}
if (this.includeSource) {
this.formData['source'] = 'demo_registration';
}
console.log(this.formData);
}
}
As Joe typed away, adding more properties and conditions to his once-simple component, he felt a sinking realization. What started as a clean, reusable solution had turned into an overly complex mess of configuration options and edge cases. The component was trying to handle too many specific scenarios, making it harder to understand and maintain.
code duplication != knowledge duplication
One common argument against composable components is the perceived duplication of code. However, it's crucial to understand that code duplication != knowledge duplication.
As stated so beautifully in 'The Pragmatic Programmer' by D. Thomas and A. Hunt:
"DRY is about the duplication of knowledge, of intent."
Not about "don't copy-and-paste lines of source".
Consider the scenario of the configurable component of Joe: Two different forms in the application might be composed of the exact same set of smaller components. At first glance, this may seem like duplication. However, the forms serve different logical purposes - perhaps one is for user registration and the other for a demo.
The similarity in structure is merely coincidental. Each form represents a distinct concept in your domain model, and the reuse of components is a testament to their well-designed, modular nature.
Boolean inputs
Joe moved away from using multiple boolean inputs that created, complexity, instead creating specialized components that handle specific behaviors.
The boolean inputs in a component can be a bad thing. In the first example of Joe, it is. It is very similar to the code smell "Flag argument", as explained so well by Martin Fowler.
Summary
While the configurable approach offers flexibility and seems like it's DRY, it can lead to:
- Increased complexity in templates
- Difficulty in adding specific behaviors to individual fields
- Challenges in maintaining type safety
- Less intuitive component usage
The composable component offers several compelling advantages:
- Flexibility: Easily modify one form without affecting the other
- Readability: The structure of the form is clear and easy to understand at a glance, enhancing code comprehension.
- Testability: Smaller, focused components are easier to unit test, leading to more robust code.
- Separation of Concerns: Each component encapsulates its own logic and presentation, adhering to Angular's component-based architecture.
- Scalability: Scale your application by composing new forms from existing components
SOCIAL SHARE CARD GENERATOR