How to use reactive forms within ngFor

13,740

Solution 1

To solve your problem you have to use formArray as follows:

In the component:

export class UserComponent implements OnInit {
    usersForm: FormGroup;
    errorMessage : string;

    constructor(private formBuilder: FormBuilder) {}

    ngOnInit() {
        this.usersForm= this.formBuilder.group({
            users: this.formBuilder.array([
                this.formBuilder.group({
                    address: [null, [Validators.required]],
                    phone: [null, [Validators.required]]
                })
            ])
        });
    }

    initUserRow(): FormGroup {
        return this.formBuilder.group({
            address: [null, [Validators.required]],
            phone: [null, [Validators.required]],
        });
    }

    addUserRow(): void {
        const usersArray=
            <FormArray>this.usersForm.controls['users'];
        usersArray.push(this.initUserRow());
    }

    removeUserRow(rowIndex: number): void {
        const usersArray= <FormArray>this.usersForm.controls['users'];
        if (usersArray.length > 1) {
            usersArray.removeAt(rowIndex);
        } else {
            this.errorMessage = 'You cannot delete this row! form should contain at least one row!';
            setTimeout(() => {
                this.errorMessage = null;
            }, 4000);
        }
    }

}

In the view:

<form *ngIf="usersForm" [formGroup]="usersForm" (ngSubmit)="createUsers()">
    <table class="table table-sm table-bordered">
        <thead>
        <tr class="text-center">
            <th>Address</th>
            <th>Phone</th>
            <th>Action</th>
        </tr>
        </thead>
        <tbody formArrayName="users">
        <tr *ngFor="let item of usersForm.controls.users.controls; let $index=index" [formGroupName]="$index">
            <td style="min-width: 120px">
                <input class="form-control" type="text" formControlName="address"/>
                <div class="text-danger" *ngIf="usersForm.controls['users'].controls[$index].controls['address'].touched
                             && usersForm.controls['users'].controls[$index].controls['address'].hasError('required')">
                    Please enter address!
                </div>
            </td>

            <td style="min-width: 120px">
                <input class="form-control" type="text" formControlName="address"/>
                <div class="text-danger" *ngIf="usersForm.controls['users'].controls[$index].controls['phone'].touched
                             && usersForm.controls['users'].controls[$index].controls['phone'].hasError('required')">
                    Please enter phone number!
                </div>
            </td>

            <td style="width: 100px">
                <button (click)="addUserRow()" class="btn btn-success btn-sm mr-1" type="button"><i class="fa fa-plus"></i></button>
                <button (click)="removeUserRow($index)" class="btn btn-danger btn-sm" type="button"><i class="fa fa-times"></i></button>
            </td>
        </tr>
        </tbody>
    </table>
</form>

Hope now your problem will be solved!

Solution 2

With reactive forms, you define the data structure to hold the form data in your code. In your example, you define it with this.form. But your form definition only has one address control and one phone control. So you've told Angular to only store one address and one phone.

To allow for multiple address/phone sets, you need a FormArray as mentioned by @DavidZ. As its name implies, a FormArray allows you to have an array of form values.

In your example, your FormArray would be comprised of a FormGroup with one entry for each user. The FormGroup would be comprised of the set of FormControls: the address and the phone.

Form Component

This example has a name and an array of addresses, but should give you a general idea:

ngOnInit(): void {
    this.customerForm = this.fb.group({
        name: ['', [Validators.required, Validators.minLength(3)]],
        addresses: this.fb.array([this.buildAddress()])
    });
}

buildAddress(): FormGroup {
    return this.fb.group({
            addressType: 'home',
            street1: ['', Validators.required],
            street2: '',
            city: '',
            state: '',
            zip: ''
    });
}

You can find the complete code for this here: https://github.com/DeborahK/Angular2-ReactiveForms/tree/master/Demo-Final-Updated

Share:
13,740

Related videos on Youtube

Present practice
Author by

Present practice

Updated on June 04, 2022

Comments

  • Present practice
    Present practice almost 2 years

    An array of users can have multiple elements (users). In a template, I iterate over this array and add a form for each element. Right now each form binds to the same form group values. If I fill out the form for the first user, submit buttons of all the forms become enabled. How to point each form to the unique formGroup or unique variables?

    component:

    import { FormControl, FormGroup, Validators, FormBuilder} from '@angular/forms';
    ...
    export class UserComponent implements OnInit {
     form: FormGroup;
     @input users: any[];
    
     constructor(private _fb: FormBuilder) {}
     ngOnInit() {
      this.form = this._fb.group({
       address: new FormControl('', [Validators.required]),
       phone: new FormControl('', [Validators.required]),
       });
     }
    
     add() {
       if (this.form.valid){
         // code
       }
     }
    

    Template:

     <div *ngFor="let user if users">
      {{user.name}}
        <form [formGroup]="form"  (ngSubmit)="add()">         
         <div class="form-group">
           <label>Address:</label>
           <input formControlName="address" class="form-control">
           <label>Phone:</label>
           <input formControlName="phone" class="form-control"></input>
         </div>
         <button type="submit" class="submit" [disabled]="!form.valid">Submit</button>    
        </form>
     </div>