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.
userForm = new FormGroup({
userHobby: new FormArray([], [Validators.required]),
});
When the user clicks Add New Row, we push a new FormGroup into the array:
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.
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:
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:
Collect all the
skillvalues.Check if any duplicates exist.
If yes, mark all the duplicates with
{ duplicate: true }.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 here:

