Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Intelligence View
⚡ tsecurity.de Intelligence

Future-Proofing Components: The Power of Composable Components

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.

This design philosophy emphasizes building applications by assembling smaller, reusable components rather than creating monolithic structures. By leveraging composability, developers can enhance code readability, testability, and scalability while adhering to best practices in software engineering.

The Evolution of Component Design

Initial Approach: Simple Component

Initially, developers often create straightforward components to handle specific tasks. For instance, a basic user registration form might look like this:

/**
 * Note: The examples in this article are for angular, but this pattern can be used in any framework.
 */
@Component({
  selector: 'app-user-registration',
  template: `
    <form (ngSubmit)="submitForm()">
      <div>
        <label>Full Name</label>
        <input type="text" name="fullName" placeholder="Enter your full name">
      </div>
      <div>
        <label>Email</label>
        <input type="email" name="email" placeholder="Enter your email">
      </div>
      <div>
        <label>Password</label>
        <input type="password" name="password" placeholder="Enter your password">
      </div>
      <button type="submit">Register</button>
    </form>
  `
})
export class UserRegistrationComponent { ... }

As applications grow, it is tempting to create more reusable components. Components which use input parameters for the different use-cases.

This leads to the creation of configurable components:

@Component({
  selector: 'app-form',
  template: `
    <form (ngSubmit)="submitForm()">
      @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]"
          >
        </div>
      }
      <button type="submit">Submit</button>
    </form>
  `
})
export class FormComponent {
  @Input() formFields!: Array<{label: string, type: string, name: string, placeholder: string}>;
  @Output() formSubmit = new EventEmitter<Record<string, string>>();

  formData: Record<string, string> = {};

  submitForm() {
    this.formSubmit.emit(this.formData);
  }
}

This configurable component can be used for different forms.

First the revised user registration form:

@Component({
  selector: 'app-user-registration',
  template: `<app-form [formFields]="registerFormFields" (formSubmit)="submit()" />`
})
export class UserRegistrationComponent {
  registerFormFields = [
      { label: 'Full Name', type: 'text', name: 'fullName', placeholder: 'Enter your full name' },
      { label: 'Email', type: 'email', name: 'email', placeholder: 'Enter your email' },
      { label: 'Password', type: 'password', name: 'password', placeholder: 'Enter your password' }
    ];
  }

  // ...submit method

And second for example a demo form:

@Component({
  selector: 'app-demo-registration',
  template: `
    <app-form [formFields]="demoFormFields"></app-registration-form>
  `
})
export class DemoRegistrationComponent {
  demoFormFields = [
    { label: 'Full Name', type: 'text', name: 'fullName', placeholder: 'Enter your full name' },
    { label: 'Email', type: 'email', name: 'email', placeholder: 'Enter your email' },
    { label: 'Company', type: 'text', name: 'company', placeholder: 'Enter your company name' },
    { label: 'Role', type: 'text', name: 'role', placeholder: 'Enter your role' }
  ];
}

But then comes the twist.

Imagine that for the first form, you need to add e-mail validation specific to that form.

You can add it to the main form as a boolean property:

@Component({
  selector: 'app-form',
  template: `
    <form>
      @for (field of formFields; track field.name) {
        <div>
          <label>{{ field.label }}</label>
          <input 
            [type]="field.type" 
            [name]="field.name" 
            [placeholder]="field.placeholder"
          >
          <span *ngIf="field.name === 'email' && validateEmailDomain">
            * 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;
}

This means you can turn it off or on using that property. Just one boolean right?

But then another curveball. A different user story requires you to have an additional field in the submit payload. This should only be present in the demo form.

More additional logic is added:

@Component({
  selector: 'app-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 time passes, more exceptions are added, especially in large organizations with many developers. This approach can lead to overly complex components with numerous configuration options and edge cases.

A good rule of thumb is that the main purpose of the component becomes too diverse. It tries to solve too many things.

The power of Composable Components

Composable components offer a more flexible and maintainable solution. By breaking down forms into smaller, specialized components, developers can create more adaptable and easier-to-maintain code.

Let's see what this would look like. First for the registration form:

// Regular Registration Form
@Component({
  selector: 'app-user-registration',
  template: `
    <form (submit)="onSubmit()">
      <app-text-input label="Full Name" name="fullName" placeholder="Enter your full name" />
      <app-email-input 
        label="Email" 
        name="email" 
        placeholder="Enter your email"
        [validateDomain]="true"
      />
      <app-password-input label="Password" name="password" placeholder="Enter your password" />
      <button type="submit">Register</button>
    </form>
  `
})

Doing so, immediately highlights that the logic of the e-mail validation should be placed in the e-mail input component. Making it both reusable and logically placed.

This approach adheres to the principle of separation of concerns, ensuring that email-related validation is handled where it belongs - in the email input component itself.

@Component({
  selector: 'app-email-input',
  standalone: true,
  imports: [FormsModule],
  template: `
    <div>
      <label [for]="name">{{ label }}</label>
      <input 
        type="email" 
        [id]="name"
        [name]="name" 
        [placeholder]="placeholder" 
        [(ngModel)]="email"
        (ngModelChange)="onEmailChange($event)"
      >
      @if (validateDomain) {
        <span>* Email domain will be validated</span>
      }
    </div>
  `
})
export class EmailInputComponent {
  @Input() label: string;
  @Input() name: string;
  @Input() placeholder: string;
  @Input() validateDomain = false;
  @Output() emailChange = new EventEmitter<string>();

  email: string = '';

  onEmailChange(value: string) {
    this.emailChange.emit(value);
    if (this.validateDomain) {
      this.validateEmailDomain(value);
    }
  }

  private validateEmailDomain(email: string) {
    // Implement domain validation logic here
    console.log(`Validating email domain for: ${email}`);
  }
}

Then for the demo registration form:

@Component({
  selector: 'app-demo-registration',
  template: `
    <form (ngSubmit)="onSubmit()">
      <app-text-input label="Full Name" name="fullName" placeholder="Enter your full name" />
      <app-email-input label="Email" name="email" placeholder="Enter your email" />
      <app-text-input label="Company" name="company" placeholder="Enter your company name" />
      <app-text-input label="Role" name="role" placeholder="Enter your role" />
      <button type="submit">Register for Demo</button>
    </form>
  `
})
export class DemoRegistrationComponent {
  @Output() formSubmit = new EventEmitter<Record<string, string>>();

  onSubmit() {
    // Gather form data
    const formData = {}; // Collect data from child components
    formData['source'] = 'demo_registration'; // Add source tracking
    this.formSubmit.emit(formData);
  }
}

The composable approach does not only solve the immediate problems but also opens up new possibilities for creating flexible, maintainable forms across the entire application.

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 is not knowledge duplication.

As stated so beautifully in 'The Pragmatic Programmer' by D. Thomas and A. Hunt:

"Don't Repeat Yourself (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: 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 - 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

Boolean inputs in a component can be a bad thing. In the first example of this article, 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
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Future-Proofing Components: The Power of Composable Components

Thematisch verwandte Begriffe: FutureProofing, Components, Power, Composable · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61591 | djust provides Phoenix LiveView-style reactive server-side rendering for…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Rechts: Artikel Ziehen Links: RSS
Hoch: nächster Artikel Runter: zurück / schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Rechts: Original Links: RSS-Ansicht
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick