<form class="password-validator" [formGroup]="passwordForm">
<div class="password-validator-fields">
<app-field
class="mb-24"
[ngClass]="{ 'state-error': !isFormValid && passwordForm.get('password')?.touched }"
[fieldType]="FieldType.PASSWORD"
[label]="'SHARED.PASSWORD' | transloco"
[isIcon]="true"
[isIconRight]="true"
[fieldState]="passwordFieldState"
formControlName="password"
></app-field>
<app-field
class="mb-24"
[ngClass]="{ 'state-error': !isFormValid && passwordAgainTouched }"
[fieldType]="FieldType.PASSWORD"
[label]="'SHARED.PASSWORD_AGAIN' | transloco"
[isIcon]="true"
[isIconRight]="true"
[fieldState]="passwordAgainFieldState"
formControlName="passwordAgain"
></app-field>
</div>
<div class="password-validator-message">
<app-password-check
[validationText]="'AUTH.MIN_CHARACTER' | transloco"
[isValid]="isMinCharacter"
></app-password-check>
<app-password-check
[validationText]="'AUTH.MIN_LOWERCASE' | transloco"
[isValid]="hasMinLowercase"
></app-password-check>
<app-password-check
[validationText]="'AUTH.MIN_UPPERCASE' | transloco"
[isValid]="hasMinUppercase"
></app-password-check>
<app-password-check
[validationText]="'AUTH.MIN_NUMBER' | transloco"
[isValid]="hasMinNumber"
></app-password-check>
<app-password-check
*ngIf="passwordsMismatch && passwordAgainTouched"
[validationText]="'AUTH.PASSWORD_MISMATCH' | transloco"
[isValid]="!passwordsMismatch"
></app-password-check>
</div>
</form>
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
ValidatorFn,
Validators,
} from '@angular/forms';
import { FieldComponent } from '@app/lib/components/field/field.component';
import { FieldState, FieldType } from '@app/lib/components/field/field.enum';
import { PasswordCheckComponent } from '@app/lib/components/password-check/password-check.component';
import { TranslocoPipe } from '@jsverse/transloco';
@Component({
selector: 'app-password-validator',
standalone: true,
imports: [CommonModule, FieldComponent, TranslocoPipe, PasswordCheckComponent, ReactiveFormsModule],
templateUrl: './password-validator.component.html',
styleUrls: ['./password-validator.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PasswordValidatorComponent implements OnInit {
passwordForm: FormGroup;
@Input() validationText: string;
isFormValid: boolean = false;
FieldType = FieldType;
FieldState = FieldState;
constructor(private readonly fb: FormBuilder) {
this.passwordForm = this.fb.group(
{
password: ['', [Validators.required, Validators.minLength(8)]],
passwordAgain: ['', Validators.required],
},
{ validators: this.passwordsMatch() },
);
}
ngOnInit(): void {
this.passwordForm.valueChanges.subscribe(() => {
this.checkFormValidity();
});
}
private passwordsMatch(): ValidatorFn {
return (formGroup: AbstractControl): ValidationErrors | null => {
const passwordControl = formGroup.get('password');
const passwordAgainControl = formGroup.get('passwordAgain');
if (!passwordControl || !passwordAgainControl) {
return null;
}
const password = passwordControl.value;
const passwordAgain = passwordAgainControl.value;
return password === passwordAgain ? null : { passwordsMismatch: true };
};
}
get isMinCharacter(): boolean {
return (this.passwordForm.get('password')?.valid && this.passwordForm.get('password')?.value?.length >= 8) || false;
}
get hasMinLowercase(): boolean {
return /[a-z]/.test(this.passwordForm.get('password')?.value || '');
}
get hasMinUppercase(): boolean {
return /[A-Z]/.test(this.passwordForm.get('password')?.value || '');
}
get hasMinNumber(): boolean {
return /[0-9]/.test(this.passwordForm.get('password')?.value || '');
}
get passwordsMismatch(): boolean {
return this.passwordForm.hasError('passwordsMismatch');
}
get passwordAgainTouched(): boolean {
return this.passwordForm.get('passwordAgain')?.touched || false;
}
get passwordFieldState(): FieldState {
return !this.isFormValid && this.passwordForm.get('password')?.touched
? this.FieldState.ERROR
: this.FieldState.DEFAULT;
}
get passwordAgainFieldState(): FieldState {
return !this.isFormValid && this.passwordAgainTouched ? this.FieldState.ERROR : this.FieldState.DEFAULT;
}
private checkFormValidity(): void {
this.isFormValid =
this.isMinCharacter &&
this.hasMinLowercase &&
this.hasMinUppercase &&
this.hasMinNumber &&
!this.passwordsMismatch &&
this.passwordForm.valid;
console.log(this.isFormValid ? 'Valid' : 'Not valid');
}
}