I would solve it like this:
https://stackblitz.com/edit/form-array-angular-ax9vna?file=src%2Fapp%2Fapp.component.ts
my tips and tricks
I like when the validation is there where the error should show. So the control itself is handling its errors
property.
make sure your controls know when something changed
this.sub = this.usernamesArr.valueChanges.subscribe(_ => updateControls(this.usernamesArr) );
we need to access the formArray-value in our username controls. By binding
this
we will have access to it.
uniqueInArray = uniqueInArray.bind(this);
(copied code from stackblitz)
html
<div [formGroup]="usernamesArr"
*ngFor="let item of usernamesArr.controls; let i = index;">
<div [formGroupName]="i">
<input formControlName="username" [placeholder]="'username' + i">
<pre style="color: red">
{{usernamesArr.controls[i].controls.username.errors | json}}
</pre>
</div>
</div>
<button (click)="addItem()">Add</button>
ts
import { Component } from "@angular/core";
import {
FormBuilder,
FormGroup,
FormArray,
FormControl,
Validators,
AbstractControl
} from "@angular/forms";
import { Subscription } from "rxjs";
const uniqueInArray = function(
control: AbstractControl
): { uniqueInArray: string } | null {
const value = control.value;
const arr = this.usernamesArr.value as { username: string }[];
if (value == null || value.length === 0 || arr == null || arr.length < 2) {
return null;
}
return arr.map(_ => _.username).filter(_ => _ == value).length > 1
? { uniqueInArray: `${value} already exists.` }
: null;
};
const updateControls = function(control: FormArray): null {
control.controls.map((c: FormGroup) =>
c.controls.username.updateValueAndValidity({
onlySelf: true,
emitEvent: false
})
);
return null;
};
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
sub: Subscription;
usernamesArr: FormArray;
uniqueInArray = uniqueInArray.bind(this);
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.usernamesArr = new FormArray([]);
this.sub = this.usernamesArr.valueChanges.subscribe(_ =>
updateControls(this.usernamesArr)
);
}
createItem(): AbstractControl {
return new FormGroup({
username: new FormControl("", [Validators.required, this.uniqueInArray])
});
}
addItem(): void {
this.usernamesArr.push(this.createItem());
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}