Angular form validators for passwords, emails, universal constraints, and credit cards with reactive and template-driven form support
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Utility classes and functions for form control manipulation, validation state management, and specialized validation scenarios.
Utility class providing helper methods for Angular AbstractControl manipulation and validation state management.
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)); // falseImplementation Logic:
true if value is undefined or nulltrue if value is empty string ""false for all other values (including 0, false, empty arrays/objects)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:
null, the method does nothingerrorId, adds the new errorProgrammatically 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:
null, the method does nothingnullSpecialized validator for ensuring two form controls have equal values, commonly used for password confirmation scenarios.
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:
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>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;
}
};
}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'
});
}
};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:
OnDestroy to unsubscribe from observablesdelay(1) operator to prevent validation loops{ notEqualTo: true } when values don't matchError Object:
{
notEqualTo: true
}Implementation Notes:
valueChanges observabledelay(1) operator prevents infinite validation loopsngModel) and reactive forms (formControlName, formControl)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>