DEV Community

Cover image for Angular Forms: Building Complex Reactive Forms with ngJoyValidators
Bledar Ramo
Bledar Ramo

Posted on

Angular Forms: Building Complex Reactive Forms with ngJoyValidators

angular form complexity

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)
  )]
});

Enter fullscreen mode Exit fullscreen mode

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.')]]
});  
Enter fullscreen mode Exit fullscreen mode
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.' )] 
});  
Enter fullscreen mode Exit fullscreen mode

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")]
})  
Enter fullscreen mode Exit fullscreen mode

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()]
 })  
Enter fullscreen mode Exit fullscreen mode

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()]));  

Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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.') ]],   
Enter fullscreen mode Exit fullscreen mode

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.') 
]], 
Enter fullscreen mode Exit fullscreen mode

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.')]] 
});  
Enter fullscreen mode Exit fullscreen mode

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()]]  
}); 
Enter fullscreen mode Exit fullscreen mode

INTERESTED FOR MORE INFO

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

Top comments (2)

Collapse
 
stealthmusic profile image
Jan Wedel • Edited

When I started with frontend dev, forms seemed to be the most boring and easy thing to do… until I had to do my first form.

I still don’t fully understand, why it has to be that complicated. But your library really looks great to simply a lot of rather complex tasks! Thanks for sharing that.

Collapse
 
bledar_ramo profile image
Bledar Ramo

When we consider forms, our minds often go to contact forms or something similar. However, in reality, forms are utilized everywhere , banking, invoicing, booking, cloud services, and more. These applications heavily rely on forms for configuring , editing and submitting data. Therefore, it is crucial to develop forms based on the right principles and with the appropriate flexibility.