Search code examples
javascriptangulartypescriptangular-reactive-formsformarray

Custom Validations in Angular FormArray (Sum of proportion must be 100%)


I have created a formArray in angular to represent Nominee's distribution which has two fields Name and Proportion. Plus button(+) adds a new row and (X) button deletes current row. Now I want to code for a validation such that total of proportion column must be 100% (No matter how many nominees' name it consists)
Ex.
No Name Proportion
1 Andy 100
(Sum of proportion is 100)
----------
Ex.2
No Name Proportion
1 Andy 60
2 Bruce 40
(Sum of proportion is 100)
----------
Ex.3
No Name Proportion
1 Andy 60
2 Bruce 20
3 Ciao 20
(Sum of proportion is 100)
----------

Here is my component.html code

<h5>Nominees</h5>
                <div class="row">
                    <form novalidate [formGroup]="FormNominees">

                        <div clas="col-xs-12 form-group marL40">
                            <div formGroupName="itemRows">
                                <ng-container *ngIf="FormNominees.controls.itemRows!=null">
                                    <div *ngFor="let itemrow of FormNominees.controls.itemRows.controls; let i = index"
                                        [formGroupName]="i">
                                        <div class="row">
                                            <mat-form-field
                                                class="example-full-width d-block input-small-size col-sm-2.4"
                                                appearance="outline">
                                                <input matInput placeholder="Name" formControlName="name">
                                                <mat-error *ngIf="f9.name.touched && f9.name.errors?.required">It is mandatory
                                                </mat-error>
                                                <mat-error *ngIf="f9.name.touched && f9.name.errors?.pattern">Can only contain characters.
                                                </mat-error>
                                            </mat-form-field>

                                            <mat-form-field
                                                class="example-full-width d-block input-small-size col-sm-2.4"
                                                appearance="outline">
                                                <input matInput placeholder="Relationship"
                                                    formControlName="relationship">
                                                    <mat-error *ngIf="f9.relationship.touched && f9.relationship.errors?.required">It is mandatory
                                                    </mat-error>
                                            </mat-form-field>
                                            
                                            <mat-form-field
                                                class="example-full-width d-block input-small-size col-sm-2.4"
                                                appearance="outline">
                                                <mat-select formControlName="gender" placeholder="Gender">
                                                    <mat-option value="Male">Male</mat-option>
                                                    <mat-option value="Female">Female</mat-option>
                                                    <mat-option value="Other">Other</mat-option>
                                                </mat-select>
                                                <mat-error *ngIf="f9.gender.touched && f9.gender.errors?.required">It is mandatory
                                                </mat-error>
                                            </mat-form-field>

                                            <mat-form-field
                                                class="example-full-width d-block input-small-size col-sm-2.4"
                                                appearance="outline">
                                                <input matInput placeholder="phone" formControlName="phone">
                                                <mat-error *ngIf="f9.phone.touched && f9.phone.errors?.required">It is mandatory
                                                </mat-error>
                                                <mat-error *ngIf="f9.phone.touched && f9.phone.errors?.pattern">Can only contain numbers.
                                                </mat-error>
                                            </mat-form-field>

                                            <mat-form-field
                                                class="example-full-width d-block input-small-size col-sm-2.4"
                                                appearance="outline">
                                                <input matInput placeholder="Proportion"
                                                    formControlName="gratuityProportion">
                                                     <mat-error *ngIf="f9.gratuityProportion.touched && f9.gratuityProportion.errors?.required">It is mandatory
                                                    </mat-error>
                                                    <mat-error *ngIf="f9.gratuityProportion.touched && f9.gratuityProportion.errors?.pattern">Can only contain numbers.
                                                    </mat-error> 
                                                    <mat-error *ngIf="f9.gratuityProportion.touched && f9.gratuityProportion.errors?.proportionValidator">Total must be 100%.
                                                    </mat-error> 

                                            </mat-form-field>
                                            
                                            <div class="col-sm-2.4">
                                                <button (click)="deleteRow(i)" class="btn btn-danger">x</button>
                                                <!-- </div>
                                            <div class="form-group"> -->
                                                <button type="button" (click)="addnewRow()"
                                                    [disabled]="FormNominees.invalid" 
                                                    class="btn btn-primary">+</button>
                                            </div>
                                        </div>
                                    </div>
                                </ng-container>
                            </div>
                        </div>

                    </form>
                </div> <br>

Here is my component.ts code

        import { proportionValidator } from './proportion.validator';
        @Component({
          selector: 'app-component',
          templateUrl: './component.component.html',
          styleUrls: ['./component.component.scss']
        })
        export class GratuityComponent implements OnInit {
      FormNominees: FormGroup;
      TotalRow: number;
      itemFB: any;
    
    constructor(private fb: FormBuilder) {
    
   }
ngOnInit(): void {

this.FormNominees = this.fb.group({
      itemRows: this.fb.array([this.initItemRow()])
    });
  initItemRow() {
    this.itemFB =  this.fb.group({
      name: ['', [Validators.required, Validators.pattern('[a-zA-Z0-9. ]*' )]],
      relationship: ['', Validators.required],
      phone: ['', [Validators.required, Validators.pattern('[a-zA-Z0-9. ]*' )]],
      gratuityProportion: ['', [Validators.required, Validators.pattern('^[0-9]*$'), proportionValidator] ],
      gender:['', Validators.required],
      
      employeeUniqueId: '00000001-0001-0001-0001-000000000001'
    })
    return this.itemFB;
  }

  addnewRow() {
    const control = <FormArray>this.FormNominees.controls['itemRows'];
    control.push(this.initItemRow())
  }

  deleteRow(index: number) {
    const control = <FormArray>this.FormNominees.controls['itemRows'];
    // control.push(this.initItemRow())
    if (control != null) {
      this.TotalRow = control.value.length;
    }
    if (this.TotalRow > 1) {
      control.removeAt(index);
    } else {
      alert('One record is mandatory');
      return false;
    }
  }
  get f9() {return this.itemFB.controls;}

}
    
    }

Now, my question is what code should I write in proportion.validator.ts to achieve the condition?


Solution

  • Straight from the docs:

    const arr = new FormArray(
      [
        new FormControl('Nancy'),
        new FormControl('Drew')
      ], 
      {
       validators: myValidator //<- this is where your proportion validator goes
      }
    );
    

    In your case, it looks like this:

        this.FormNominees = this.fb.group({
          itemRows: this.fb.array(
            [
              this.initItemRow()
            ],
            { 
               validators: proportionValidator 
            }
          )
        });