CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ngx-validators

Angular form validators for passwords, emails, universal constraints, and credit cards with reactive and template-driven form support

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

form-control-utilities.mddocs/

Form Control Utilities

Utility classes and functions for form control manipulation, validation state management, and specialized validation scenarios.

Capabilities

AbstractControlUtil

Utility class providing helper methods for Angular AbstractControl manipulation and validation state management.

Control Presence Check

Determines if a control has a meaningful value (not null, undefined, or empty string).

/**
 * Checks if control has a meaningful value
 * @param control - AbstractControl to check
 * @returns true if control value is null, undefined, or empty string
 */
static isNotPresent(control: AbstractControl): boolean;

Usage Example:

import { AbstractControlUtil } from "ngx-validators";
import { FormControl } from "@angular/forms";

const control1 = new FormControl(null);
const control2 = new FormControl('');
const control3 = new FormControl('value');
const control4 = new FormControl(0);

console.log(AbstractControlUtil.isNotPresent(control1)); // true
console.log(AbstractControlUtil.isNotPresent(control2)); // true  
console.log(AbstractControlUtil.isNotPresent(control3)); // false
console.log(AbstractControlUtil.isNotPresent(control4)); // false

Implementation Logic:

  • Returns true if value is undefined or null
  • Returns true if value is empty string ""
  • Returns false for all other values (including 0, false, empty arrays/objects)

Error Management

Add Error

Programmatically adds a validation error to a control.

/**
 * Adds a validation error to the control
 * @param control - AbstractControl to add error to (can be null)
 * @param errorId - String identifier for the error
 * @param value - Error value/details
 */
static addError(control: AbstractControl | null, errorId: string, value: any): void;

Usage Example:

import { AbstractControlUtil } from "ngx-validators";
import { FormControl } from "@angular/forms";

const control = new FormControl('invalid-value');

// Add custom validation error
AbstractControlUtil.addError(control, 'customRule', { 
  message: 'Value does not meet custom criteria',
  expected: 'valid-format',
  actual: 'invalid-value'
});

console.log(control.errors);
// Output: { customRule: { message: '...', expected: '...', actual: '...' } }

// Add simple boolean error
AbstractControlUtil.addError(control, 'required', true);

console.log(control.errors);
// Output: { customRule: {...}, required: true }

Behavior:

  • If control is null, the method does nothing
  • If control has no existing errors, creates new errors object with the specified error
  • If control has existing errors but not the specified errorId, adds the new error
  • If the error already exists, does nothing (doesn't overwrite)
Remove Error

Programmatically removes a specific validation error from a control.

/**
 * Removes a specific validation error from the control
 * @param control - AbstractControl to remove error from (can be null)
 * @param errorId - String identifier of the error to remove
 */
static removeError(control: AbstractControl | null, errorId: string): void;

Usage Example:

const control = new FormControl('', {
  validators: [/* some validators */]
});

// Assume control has multiple errors
console.log(control.errors);
// Output: { required: true, minlength: { requiredLength: 5, actualLength: 0 } }

// Remove specific error
AbstractControlUtil.removeError(control, 'required');

console.log(control.errors);
// Output: { minlength: { requiredLength: 5, actualLength: 0 } }

// Remove the last error
AbstractControlUtil.removeError(control, 'minlength');

console.log(control.errors);
// Output: null (errors object is cleared when no errors remain)

Behavior:

  • If control is null, the method does nothing
  • If control has no errors or doesn't have the specified error, does nothing
  • If removing the error would leave other errors, removes only the specified error
  • If removing the error would leave no errors, sets control errors to null

EqualToValidator

Specialized validator for ensuring two form controls have equal values, commonly used for password confirmation scenarios.

Equal To Validation

Creates a validator function that compares two controls within the same form group.

/**
 * Creates validator that ensures two controls have equal values
 * @param c1Name - Name of the first control to compare
 * @param c2Name - Name of the second control to compare
 * @returns ValidatorFn that validates equality at form group level
 */
static equalTo(c1Name: string, c2Name: string): ValidatorFn;

Usage Example:

import { EqualToValidator } from "ngx-validators";
import { FormBuilder, FormGroup } from "@angular/forms";

export class RegistrationComponent {
  registrationForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.registrationForm = this.fb.group({
      password: [''],
      confirmPassword: [''],
      email: [''],
      confirmEmail: ['']
    }, {
      validators: [
        EqualToValidator.equalTo('password', 'confirmPassword'),
        EqualToValidator.equalTo('email', 'confirmEmail')
      ]
    });
  }
}

Advanced Usage with Custom Control Names:

const userForm = this.fb.group({
  newPassword: [''],
  passwordVerification: [''],
  primaryEmail: [''],
  emailConfirmation: ['']
}, {
  validators: [
    EqualToValidator.equalTo('newPassword', 'passwordVerification'),
    EqualToValidator.equalTo('primaryEmail', 'emailConfirmation')
  ]
});

Error Handling:

The validator manages errors at two levels:

  1. Form Group Level: Returns validation error object if controls don't match
  2. Control Level: Directly adds/removes notEqualTo error on the second control
// Form group level error (returned by the validator)
if (form.errors?.equalTo) {
  console.log('Form has equality validation errors');
}

// Control level error (added directly to the second control)
const confirmControl = form.get('confirmPassword');
if (confirmControl?.errors?.notEqualTo) {
  console.log('Confirmation password does not match');
}

Template Usage:

<form [formGroup]="registrationForm">
  <input formControlName="password" type="password" placeholder="Password">
  
  <input formControlName="confirmPassword" type="password" placeholder="Confirm Password">
  
  <!-- Show error from the control level -->
  <div *ngIf="registrationForm.get('confirmPassword')?.errors?.notEqualTo">
    Passwords do not match.
  </div>
  
  <!-- Show error from the form level -->
  <div *ngIf="registrationForm.errors?.equalTo">
    Please ensure all fields match their confirmations.
  </div>
</form>

Integration with Other Validators

Using Utilities in Custom Validators

import { AbstractControlUtil } from "ngx-validators";
import { AbstractControl, ValidatorFn, ValidationErrors } from "@angular/forms";

export function customValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    // Use utility to check if control has meaningful value
    if (AbstractControlUtil.isNotPresent(control)) {
      return null; // Don't validate empty controls
    }
    
    const value = control.value;
    
    // Custom validation logic
    if (value !== 'expected-value') {
      // Use utility to add error to related control
      const relatedControl = control.parent?.get('relatedField');
      AbstractControlUtil.addError(relatedControl, 'relatedFieldError', {
        message: 'Related field must be updated when this field changes'
      });
      
      return { customValidation: { expected: 'expected-value', actual: value } };
    } else {
      // Use utility to remove error from related control
      const relatedControl = control.parent?.get('relatedField');
      AbstractControlUtil.removeError(relatedControl, 'relatedFieldError');
      
      return null;
    }
  };
}

Combining with Password Validators

import { PasswordValidators, AbstractControlUtil, EqualToValidator } from "ngx-validators";

const passwordForm = this.fb.group({
  password: ['', [
    PasswordValidators.digitCharacterRule(1),
    PasswordValidators.lowercaseCharacterRule(1),
    PasswordValidators.uppercaseCharacterRule(1)
  ]],
  confirmPassword: ['']
}, {
  validators: [EqualToValidator.equalTo('password', 'confirmPassword')]
});

// Manual error management example
const handlePasswordChange = (newPassword: string) => {
  const confirmControl = passwordForm.get('confirmPassword');
  
  if (AbstractControlUtil.isNotPresent(confirmControl)) {
    return;
  }
  
  // Clear previous custom errors
  AbstractControlUtil.removeError(confirmControl, 'passwordStrengthMismatch');
  
  // Add custom validation if needed
  if (newPassword.length > 0 && confirmControl.value !== newPassword) {
    AbstractControlUtil.addError(confirmControl, 'passwordStrengthMismatch', {
      message: 'Confirmation password must match the new password'
    });
  }
};

EqualToDirective

Template-driven forms directive that validates two form controls have equal values, with reactive subscription management and automatic validation updates.

@Directive({
  selector: "[equalTo][ngModel], [equalTo][formControlName], [equalTo][formControl]"
})
export class EqualToDirective implements Validator, OnDestroy, OnChanges {
  @Input() equalTo: string | AbstractControl;
  
  validate(c: AbstractControl): ValidationErrors | null;
  ngOnDestroy(): void;
  ngOnChanges(changes: SimpleChanges): void;
  registerOnValidatorChange(fn: () => void): void;
}

Usage Examples:

<!-- Basic usage with ngModel -->
<form>
  <input 
    type="password"
    name="password"
    [(ngModel)]="user.password"
    #password="ngModel">
  
  <input 
    type="password"
    name="confirmPassword"
    [(ngModel)]="user.confirmPassword"
    #confirmPassword="ngModel"
    [equalTo]="'password'">
    
  <div *ngIf="confirmPassword.errors?.notEqualTo">
    Passwords do not match.
  </div>
</form>

<!-- Usage with reactive forms in template -->
<form [formGroup]="myForm">
  <input formControlName="email" type="email">
  
  <input 
    formControlName="confirmEmail" 
    type="email"
    [equalTo]="'email'">
    
  <div *ngIf="myForm.get('confirmEmail')?.errors?.notEqualTo">
    Email addresses do not match.
  </div>
</form>

Advanced Usage with AbstractControl Reference:

export class MyComponent {
  @ViewChild('passwordControl') passwordControl!: NgModel;
  
  // Can pass control reference directly
  getPasswordControl(): AbstractControl {
    return this.passwordControl.control;
  }
}
<input 
  type="password"
  name="password"
  [(ngModel)]="user.password"
  #passwordControl="ngModel">

<input 
  type="password"
  name="confirmPassword"
  [(ngModel)]="user.confirmPassword"
  #confirmPassword="ngModel"
  [equalTo]="getPasswordControl()">

Key Features:

  1. Reactive Subscription Management: Automatically subscribes to target control's value changes
  2. Memory Leak Prevention: Implements OnDestroy to unsubscribe from observables
  3. Circular Update Prevention: Uses delay(1) operator to prevent validation loops
  4. Flexible Input: Accepts either control name (string) or AbstractControl reference
  5. Error Management: Returns { notEqualTo: true } when values don't match

Error Object:

{ 
  notEqualTo: true 
}

Implementation Notes:

  • The directive creates a subscription to the target control's valueChanges observable
  • When the target control value changes, it triggers validation on the current control
  • The delay(1) operator prevents infinite validation loops
  • The directive properly cleans up subscriptions to prevent memory leaks
  • Works with both template-driven forms (ngModel) and reactive forms (formControlName, formControl)

Error State Management Best Practices

Conditional Error Display

export class FormUtilities {
  /**
   * Checks if a control should display errors
   * @param control - FormControl to check
   * @returns true if control has errors and has been touched or is dirty
   */
  static shouldShowErrors(control: AbstractControl): boolean {
    return !!(control && control.errors && (control.touched || control.dirty));
  }
  
  /**
   * Gets user-friendly error message for a control
   * @param control - FormControl with potential errors
   * @returns formatted error message or empty string
   */
  static getErrorMessage(control: AbstractControl): string {
    if (!control || !control.errors) return '';
    
    if (control.errors['required']) return 'This field is required';
    if (control.errors['notEqualTo']) return 'Values do not match';
    if (control.errors['minlength']) {
      const error = control.errors['minlength'];
      return `Minimum length is ${error.requiredLength} characters`;
    }
    
    return 'Please check this field';
  }
}

Template Usage:

<div class="form-field">
  <input formControlName="confirmPassword" type="password">
  
  <div class="error-message" 
       *ngIf="shouldShowErrors(form.get('confirmPassword'))">
    {{ getErrorMessage(form.get('confirmPassword')) }}
  </div>
</div>

docs

credit-card-validation.md

email-validation.md

form-control-utilities.md

index.md

password-validation.md

template-driven-forms.md

universal-validation.md

tile.json