The answer from AJT_82 was so useful to me, I thought I would share how I used its code and built a similar example - one that may have a more common use case that invites several people to register immediately. Like this: 
I thought this might help others, so I am adding it here.
You can see that the form is a simple array of text inputs for email messages, with a custom validator being loaded on each of them. You can see the JSON structure in the screenshot - see the Preliminary line in the template (thanks to AJT), a very useful idea, developing to check if your model and controls are connected!
So first declare the objects we need. Note that 3 blank lines are model data (which we will associate with text inputs):
public form: FormGroup; private control: FormArray; private emailsModel = { emails: ['','','']}
The constructor is clean (for easier testing, just order my userService to submit form data after submitting):
constructor( private _userService: UserService, ) {}
The form is built into the init method, including storing a reference to the emailsArray control, so we can check if its children (the actual inputs) are affected, and if so, whether they have errors:
ngOnInit() { this.fb = new FormBuilder; this.form = this.fb.group({ emailsArray: this.fb.array([]) }); this.control = <FormArray>this.form.controls['emailsArray']; this.patch(); } private patch(): void { // iterate the object model and extra values, binding them to the controls this.emailsModel.emails.forEach((item) => { this.control.push(this.patchValues(item)); }) }
This is what builds each input control (such as AbstracControl) with a validator:
private patchValues(item): AbstractControl { return this.fb.group({ email: [item, Validators.compose([emailValidator])] }) }
2 helper methods to check if the input has been affected, and if the validator raised an error (see the template to see how they are used), note that I am passing the array index value from *ngFor in the template)
private hasError(i):boolean { // const control = <FormArray>this.form.controls['emailsArray']; return this.control.controls[i].get('email').hasError('invalidEmail'); } private isTouched(i):boolean { // const control = <FormArray>this.form.controls['emailsArray']; return this.control.controls[i].get('email').touched; }
Here's the validator:
export function emailValidator(control: FormControl): { [key: string]: any } { var emailRegexp = /[a-z0-9._%+-] +@ [a-z0-9.-]+\.[az]{2,3}$/; if (control.value && !emailRegexp.test(control.value)) { return { invalidEmail: true }; } }
And the template:
<form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" class="text-left"> <div formArrayName="emailsArray"> <div *ngFor="let child of form.controls.emailsArray.controls; let i=index"> <div class="form-group" formGroupName="{{i}}"> <input formControlName="email" class="form-control checking-field" placeholder="Email" type="text"> <span class="help-block" *ngIf="isTouched(i)"> <span class="text-danger" *ngIf="hasError(i)">Invalid email address </span> </span> </div> </div> </div> <pre>{{form.value | json }}</pre> <div class="form-group text-center"> <button class="btn btn-main btn-block" type="submit">INVITE</button> </div> </form>
For what it's worth, I started with this terrible mess, but if you look at the code below, you can more easily understand the code above!
public form: FormGroup; public email1: AbstractControl; public email2: AbstractControl; public email3: AbstractControl; public email4: AbstractControl; public email5: AbstractControl; constructor( fb: FormBuilder ) { this.form = fb.group({ 'email1': ['', Validators.compose([emailValidator])], 'email2': ['', Validators.compose([emailValidator])], 'email3': ['', Validators.compose([emailValidator])], 'email4': ['', Validators.compose([emailValidator])], 'email5': ['', Validators.compose([emailValidator])], }); this.email1 = this.form.controls['email1']; this.email2 = this.form.controls['email2']; this.email3 = this.form.controls['email3']; this.email4 = this.form.controls['email4']; this.email5 = this.form.controls['email5']; }
and above 5 of these divs were used in the template - not very DRY!
<div class="form-group"> <input [formControl]="email1" class="form-control checking-field" placeholder="Email" type="text"> <span class="help-block" *ngIf="form.get('email1').touched"> <span class="text-danger" *ngIf="form.get('email1').hasError('invalidEmail')">Invalid email address</span> </span> </div>