angularvalidationangular-reactive-formsangular-validator

In reactive forms how do I create a custom validator that can validate an array of formGroup for no values?


I mean I have an order page that has a dynamic order items collection . I want to require the user to have atleast one item in that array. I understand simple usage of a custom validator but in this case I am not validating a single control but an array. How do I say 'If there are no values in the array then give error' ?

createFormGroup(order: Order): FormGroup {
const group = this.fb.group({
  id: [order.id],
  isItPurchase: [order.isItPurchase],
  orderItems: this.fb.array([])
});

order.orderItems.forEach(x => {
  var formArray = group.controls.orderItems as FormArray;
  formArray.push(this.createOrderItem(x));
});

    return group;
  }

 
 createOrderItem(item: OrderItem): FormGroup {
    const formGroup = this.fb.group({
      id: [item.id],
      name: [item.name, Validators.required],
      unitPrice: [item.unitPrice, Validators.required],
      units: [item.units, Validators.required]
    });

    return formGroup;
  }

for a simple case I would do something like

      // xxxValidator(control: AbstractControl): { [key: string]: boolean } | null {
  //   if (control.value === 'xxx') {
  //     return { 'xxxValidator': true }
  //   }
  //   return null;
  // };

EDIT 1:

I am closer. I identify when the error exist. But my template is wrong. How do I see the error in the template?

createFormGroup(order: Order): FormGroup {
    const group = this.fb.group({
      id: [order.id],
      isItPurchase: [order.isItPurchase],
      orderItems: this.fb.array([])
    },
      { validators: [this.testValidation1] }
      // { validators: [this.testValidation2] }
    );

    order.orderItems.forEach(x => {
      var formArray = group.controls.orderItems as FormArray;
      formArray.push(this.createOrderItem(x));
    });

    return group;
  }

  createOrderItem(item: OrderItem): FormGroup {
    const formGroup = this.fb.group({
      id: [item.id],
      name: [item.name, Validators.required],
      unitPrice: [item.unitPrice, Validators.required],
      units: [item.units, Validators.required]
    });

    return formGroup;
  }

  testValidation1(form: FormGroup) {
    if (form.value.orderItems.length === 0) {
      return { valOrderItems: false };
    }

    return null;
  }

    <div>
        <div *ngFor="let fg of orderItems.controls;  let i=index">
            <app-order-item-form [orderItemForm]="fg" (itemDeleted)="onItemDeleted(i)"></app-order-item-form>
        </div>
        <!-- <span *ngIf="orderForm.errors.valOrderItems"> Must be atleast one orderItem </span> -->
    </div>

Solution

  • Option 1

    You can use the required validator to validate that the form has at least one element

    
    const group = this.fb.group({
      id: [order.id],
      isItPurchase: [order.isItPurchase],
      orderItems: this.fb.array([], [Validators.required])
    });
    

    Now to get If the array has no values you can check if the required validator fails

    const error = group.get('orderItems').hasError('required');  // True if form is empty
    

    Option 2

    Create a validator function

    function formIsEmpty(control: FormArray) {
      if (control.controls.length === 0) {
        return { noValue: true}
      }
      return null;
    }
    
    

    And in your FormGroup you can use it like

    
    const group = this.fb.group({
      id: [order.id],
      isItPurchase: [order.isItPurchase],
      orderItems: this.fb.array([], [formIsEmpty])
    });
    

    And get the error by

    group.get('orderItems').hasError('noValue');