DEV Community

Marcel Goldammer
Marcel Goldammer

Posted on

Dynamic IDs in Angular Components

Problem

In Angular development, it is common to create reusable components that need to interact with the regular HTML DOM. In some cases you need to give attributes a unique name, as in the following cases:

  • Forms: The attribute for in <label> needs a reference to a unique id of an input element.
  • Accessibility: Some elements need a specific labelling or further description via aria-labelledby or aria-describedby. These references need point to a unique id, too. While reusable components enhance maintainability and reduce code duplication, they can introduce a significant problem: duplicate IDs. When a component is instantiated multiple times within the same route or DOM, each instance would traditionally use the same id value, leading to duplication. This duplication can break the functionality of the HTML attributes relying on these id and violate HTML specifications, potentially leading to accessibility and usability issues.

Approach

To solve the problem of duplicated IDs by using reusable components, we have to generate a unique id at runtime. This ensures that every component has its own unique id to reference to. In the following example we take a look at a custom form input component shown in this Stackblitz.

Step 1: Setting Up the Component

<!-- custom-input.component.ts template -->
<label [attr.for]="inputId" class="form-label">{{ label() }}</label>
<input
  [attr.id]="inputId"
  [type]="type()"
  [attr.aria-describedby]="hasError() ? errorId : undefined"
  [formControl]="control()"
  class="form-control">
Enter fullscreen mode Exit fullscreen mode

We have a form component with a label and an input field. The for attribute of the label needs to reference to the input's id.

Step 2: Generating Unique IDs

// custom-input.component.ts
import ...;

let uniqueId = 0;

@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'custom-input',
  template: `...`,
})
  export class CustomInputComponent {
  label = input.required<string>();
  control = input.required<FormControl>();
  type = input<string>('text');

  inputId = `custom-input-${uniqueId++}`;

...

}
Enter fullscreen mode Exit fullscreen mode

We use a variable outside of the component named uniqueId. By initializing the component, the field inputId uses and increments the value of uniqueId. This ensures that no components will have the same value for inputId.

Step 3: Using the Component

// main.ts
@Component({
selector: 'app-root',
standalone: true,
template: `
  <h1>Angular Dynamic IDs</h1>
  <form [formGroup]="form" class="container-md">
    <custom-input label="Firstname" [control]="form.controls.firstname" />
    <custom-input label="Lastname" [control]="form.controls.lastname" />
    <button (click)="send()" [disabled]="form.invalid" class="btn btn-primary">Send Form</button>
  </form>
  `,
  imports: [ReactiveFormsModule, CustomInputComponent],
})
export class App {
  form = inject(FormBuilder).group({
    firstname: ['', Validators.required],
    lastname: ['', Validators.required],
  });

  send(): void {
    alert(`Hello ${this.form.value.firstname} ${this.form.value.lastname}!`);
  }
}
Enter fullscreen mode Exit fullscreen mode

In the application we can now use our newly created form component. We do not have to worry about any duplicate IDs anymore! Our first custom input (Firstname) will now use the id="custom-input-0". Same with id="custom-input-1" for our second custom input (Lastname).

Conclusion

By dynamically generating unique IDs for Angular components, you can safely reuse components across different parts of your application without running into issues of duplicate IDs. This method is essential for scenarios requiring strict ID uniqueness, such as labeling, accessibility attributes, or JavaScript DOM manipulation.

Summary of Steps:

  • Define a counter outside the component.
  • Combine a base name with the counter.
  • Apply the generated ID to relevant attributes like id, for, aria-labelledby, etc.

Following these best practices ensures your Angular applications remain modular, accessible, and free from ID conflicts.

Top comments (3)

Collapse
 
jangelodev profile image
João Angelo

Hi Marcel Goldammer,
Top, very nice and helpful !
Thanks for sharing.

Collapse
 
jbuiquan profile image
jbuiquan • Edited

Hi, thanks for your article, I was wondering if this technique was working when the component is lazy loaded and/or imported in lazy loaded modules ?

Collapse
 
marcel-goldammer profile image
Marcel Goldammer

Hi @jbuiquan, yes, it does work if you are using lazy loaded components.