Angular Forms: Building Complex Reactive Forms with ngJoyValidators
Tags: angular, reactive, validators, form
Published: 2023-06-21 12:45:14


![angular form complexity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3j44tts9hnajjzme0dr.png)


Building forms with simple validation requirements is relatively straightforward in Angular. However, as the complexity of the validation rules increases, it becomes challenging to maintain clean and manageable code. Cross-field validation, where the value of one or more  field depends on the value of another field, and dynamic validation, where the validation rules change based on user interactions, add an extra layer of complexity.

To address these challenges I created ngJoyValidators a super light on size but heavy on functionality and flexibility library, which provides a collection of powerful custom validators designed to simplify complex form validation scenarios. These validators can be easily integrated into your Angular projects, making it easier to handle cross-field validation, dynamic validation, conditionally add / remove / compare/ disable / co-require on or more validators based on a dependency value from another field or same fields

###Learning Outcomes

By the end of this section, you will achieve the following learning outcomes:

•	Understand how to implement reactive forms  with  custom complex validation scenarios in Angular.
•	Learn how to work with cross-field validation and dynamic validation in Angular.
•	Gain knowledge of advanced techniques for handling large and complex forms with reactive forms in Angular.

##PART 1 : Conditional Validators with ngJoyValidators

In the productListingForm, we have leveraged the power of conditionalValidatorsto handle complex validation scenarios. By utilizing conditionalValidators, we can dynamically apply validators to form controls based on specific conditions. This allows us to create a flexible and robust form that adapts to user input.

For example, the productDetails field is conditionally required based on the selected productCategory. Using conditionalValidators, we can specify that the productDetails field should be required if the product category is either 'electronics' or 'books'. This ensures that users provide additional details for these specific categories, while not enforcing the requirement for other categories.

Additionally, we have implemented conditional validation for fields like reviewComments, promotionPrice, and shippingCost. Each of these fields has its own set of conditions that determine whether they should be required or have other validation rules applied. This level of flexibility allows us to create a dynamic form that adapts to different scenarios and user input.



```
this.productListingForm = this.fb.group({
  productName: ['', Validators.required],
  productPrice: ['', [Validators.required, Validators.min(1)]],
  productCategory: ['', Validators.required],
  productDetails: ['', this.conditionalValidators<string>('productCategory',
      [Validators.required],
      (productCategory) => productCategory === 'electronics' || productCategory === 'books'
  )],
  productRating: [5, [Validators.min(1), Validators.max(5)]],
  reviewComments: ['', this.conditionalValidators<number>('productRating',
      [Validators.required],
      (productRating) => productRating < 3
  )],
  hasPromotion: [false],
  promotionPrice: ['', this.conditionalValidators<boolean>('hasPromotion',
      [Validators.required, Validators.min(1)],
      (hasPromotion, formControl) => hasPromotion === true && formControl.value < formControl.parent?.get('productPrice')?.value
  )],
  hasShipping: [true],
  shippingCost: ['', this.conditionalValidators<boolean>('hasShipping',
      [Validators.required, Validators.min(0)],
      (hasShipping, shippingControl) => hasShipping === true && ((shippingControl.value > 0 && shippingControl.value < formControl.parent?.get('productPrice')?.value * 0.1) || shippingControl.value === 0)
  )]
});

```
This approach significantly reduces the amount of boilerplate code required for conditional validation, making the form implementation more maintainable and scalable 

####conditionalRequiredValidator and coRequiredValidator

The conditionalRequiredValidator and coRequiredValidator are two useful validators provided by the ngJoyValidators library for handling conditional validation scenarios in Angular reactive forms.

The conditionalRequiredValidator allows you to conditionally make a form control required based on a specific condition. For example, if you have a form field that should only be required when another field meets a certain condition, you can use the conditionalRequiredValidator to dynamically apply the required validation. It simplifies the process of making a field mandatory based on the state of other form controls.

```
this.demoForm = this.fb.group({    
  email: ['',[this.ngJoyDynamicValidatorsService.coRequiredValidator('email', 'phone', 'Either field1 or field2 is required.')]],    
  phone: ['',[this.ngJoyDynamicValidatorsService.coRequiredValidator('email', 'phone', 'Either field1 or field2 is required.')]]
});  
```
```
this.sampleForm = this.fb.group({ 
  selectField: ['defaultOption'],  
  dependentField: [null, this.conditionalRequiredValidator( 'selectField', control => control.value === 'specificOption','This field is required when specific option is selected in the dropdown.' )] 
});  
```

##Part2: FormArray custom validators 

Ensure that FormArray instance in Angular form meet the desired range  length criteria.

```
aliases: this.fb.array([    
   this.fb.control('')
   ], {validators: [this.ngJoyValidatorsService.minMaxArrayLength(3, 7, "Min 3 aliases", "Max 7 aliases")]
})  
```
The **allFieldsFilled **validator plays a crucial role in ensuring that all controls within a FormArray are filled and valid, which is particularly useful for dynamic forms where fields can be dynamically added or removed. By applying this validator to a FormArray, it iterates through each control in the array and checks if any control fails validation. If any invalid control is found, it returns an error object indicating validation failure. However, if all controls are valid, it returns null, indicating successful validation. This validator is instrumental in enforcing the requirement of filling all fields in a dynamic form before submission.

```
aliases: this.fb.array([    
   this.fb.control('')
  ],
    {validators: [this.ngJoyValidatorsService.allFieldsFilled()]
 })  
```
The **unique **validator serves a crucial purpose by checking whether the value of a specific field within a form array is unique among its sibling controls. It follows a step-by-step process:

```
this.aliases.push(this.fb.control('',[this.ngJoyValidatorsService.unique()]));  

```
Overall, the unique function guarantees that the value entered into a control within a form array remains distinct from the values of its sibling controls, preventing duplicates and ensuring data integrity.

##Part 3: Miscellaneous Validators

**Compare Password**:Check whether the value of two formControls are same or not .Below  is an example of comparing password but is can used for any controller type

```
 password: ['', [this.ngJoyValidatorsService.password({    
  validation: {    
  minLength: 5,    
  maxLength: 10,    
  digit: true,    
  specialCharacter: true    
  },message: 'Password must have at least one digit, one special character, and be between 5 and 10 characters in length.'
   }), 
  Validators.required]],
 repeatPass: ['', [this.ngJoyValidatorsService.compare('password'), Validators.required]
```


**Sanitize**: Sanitize input validator prevents the inclusion of harmful scripts or elements that could compromise the security of your web application.

```
this.validationService.sanitizeInput('Input must not contain scripting.') ]],   
```

**Script Language Type**: Check if the input text of a control follows a specific script or writing system (e.g., Latin, Cyrillic, Arabic).

```
 scriptExample: ['', [    
  this.validationService.scriptLanguageType('Latin', 'Input must only contain Latin script characters.') 
]], 
```
In addition to the validators mentioned above, the ngJoyValidators library offers a comprehensive collection of over 30 additional custom validators. These validators cover a wide range of use cases.


##Part 4: Credit Card / Bank Validators

**Credit Card**: validates if the entered credit card number is correctly formatted and matches a specific card type using algorithms such as Luhn's algorithm.

**CVV**: ensures that the provided CVV is a valid 3-digit or 4-digit number based on the card type.

**Expiry Date**: checks whether the given credit card expiration date has already passed or not.

```
this.paymentForm = this.fb.group({    
  cardNumber: ['', [this.ngJoyValidators.creditCard('Please enter a valid card number.')]],    
  expiryDate: ['', [this.ngJoyValidators.expiryDate('Card has expired.')]],    
  cvv: ['', [this.ngJoyValidators.cvv('Invalid CVV.')]] 
});  
```

**IBAN Validator**: the International Bank Account Number format to ensure the correctness of bank account numbers

```
 this.paymentForm = this.fb.group({     
  iban: ["",[this.ngJoyValidators.ibanValidator()]]  
}); 
```

---


![INTERESTED FOR MORE INFO](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j0gfvdixpk80d2aowyry.jpg)

**Interested** : in exploring full list of available custom validators , more code examples, demos, ? Check out the ngJoyValidators library at https://www.npmjs.com/package/@ngjoy.dev/reactivevalidators for a wide range of powerful validators that can enhance your form-building experience , simplify your form validation logic, solve complexity, and deliver a seamless user experience