# Detecting Duplicates in Angular FormArray

If you’ve worked with **Angular Reactive Forms**, you’ve probably built a form that dynamically grows — you click **Add**, and a new input appears. It’s clean, declarative, and *Reactive* in the truest sense.

But one fine day, you face a subtle problem:

> “How do I make sure no two inputs have the same value?”

That’s where this story begins.

## The Setup

Let’s imagine a form where users can add their **skills** dynamically.

Each skill sits inside a `FormGroup`, and all such groups live inside a `FormArray` called `userHobby`.

```typescript
userForm = new FormGroup({
  userHobby: new FormArray([], [Validators.required]),
});
```

When the user clicks *Add New Row*, we push a new `FormGroup` into the array:

```typescript
addNewRow(): void {
  this.userHobbyControl.push(
    new FormGroup({
      skill: new FormControl<string | null>(null, [
        Validators.required,
        this.uniqueSkillValidator(),
      ]),
    })
  );
}
```

Nothing fancy yet — a normal reactive form with required validation.  
The fun starts now.

---

The Problem — Detecting Duplicates  
Let’s say the user adds:

* “Angular”
    
* “React”
    
* “Angular”
    

We want to catch that third input — it shouldn’t be allowed.  
You’d think this is simple: just look for duplicates in the array and mark errors. But the challenge here is **context**.  
Each validator in Angular runs *in isolation* — it only knows about its control, not its siblings.  
So how do we make one control’s validator aware of the others?

---

## The Trick — Closing Over the Parent

The key is closure — using a function that captures the parent context (`this.userHobbyControl`) while validating each control.

```typescript
uniqueSkillValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    let skills: string[] = [];

    for (
      let index = 0;
      index <= this.userHobbyControl.controls.length;
      index++
    ) {
      const group = this.userHobbyControl.controls[index];
      const skill = group?.get('skill')?.value;
      if (skill) skills.push(skill);
    }

    if (this.hasDuplicates(skills)) {
      return { duplicate: true };
    }

    // Reset errors if no duplicates
    for (let group of this.userHobbyControl.controls) {
      group.get('skill')?.setErrors(null);
    }

    return null;
  };
}
```

Here, `uniqueSkillValidator()` creates a validator function that **closes over** the `userHobbyControl` FormArray.  
This gives it visibility into all sibling controls — and that’s how we make validation context-aware.

---

## Detecting Duplicates — The Set Trick

The check itself is beautifully simple:

```typescript
hasDuplicates(skills: string[]) {
  const noDups = new Set(skills);
  return skills.length !== noDups.size;
}
```

This is a common pattern in JavaScript — using a `Set` to strip duplicates and comparing lengths.

---

## The Validator Lifecycle

Every time the user types in any `skill`, Angular re-runs its validator.

Here’s what happens under the hood:

1. Collect all the `skill` values.
    
2. Check if any duplicates exist.
    
3. If yes, mark all the duplicates with `{ duplicate: true }`.
    
4. If not, clear all duplicate errors.
    

The form instantly reacts — that’s the reactive in Reactive Forms.

---

## Why This Works Beautifully

This approach looks deceptively simple, but it captures three deep ideas:

* **Validation by closure** — validators can be dynamic and context-aware.
    
* **State synchronization** — every keystroke re-validates all controls.
    
* **Minimal logic, maximum clarity** — the heart of good Angular design.
    

---

### TL;DR

✅ Use `FormArray` for dynamic forms.  
✅ Write a custom validator using closure to access sibling controls.  
✅ Use `Set` to detect duplicates.  
✅ Keep your form reactive — let Angular do the rest.

---

### 🧩 Try it yourself

You can explore the full working example on [StackBlitz](https://stackblitz.com/edit/unique-value-validation) here:

%[https://stackblitz.com/edit/unique-value-validation]
