Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Eonasdan/02fcb339dada46c64246a882e39e7f14 to your computer and use it in GitHub Desktop.

Select an option

Save Eonasdan/02fcb339dada46c64246a882e39e7f14 to your computer and use it in GitHub Desktop.
Angular bootstrap control display error
<ng-template #template>
<div
class="invalid-feedback"
role="alert"
*ngIf="control && control.invalid && (control.dirty || control.touched)"
>
<ng-container
*ngIf="control.errors.required || control.errors['zeroValue']"
>
Field is required.
</ng-container>
<ng-container *ngIf="control.errors.maxlength">
Field has a max length of {{ control.errors.maxlength.requiredLength }}
</ng-container>
<ng-container
*ngIf="
control.errors['Mask error'] ||
control.errors.pattern || control.errors['pattern']
"
>
Input not in correct format.
</ng-container>
<ng-container *ngIf="control.errors.email || control.errors['email']">
Please enter a valid email.
</ng-container>
<ng-container *ngFor="let error of uncoveredError">
{{ error }}
</ng-container>
<ng-container *ngIf="control.errors['dateLessThan']">
Date must be after start date
</ng-container>
<ng-container *ngIf="control.errors['typeahead']">
A value must be selected from the list.
</ng-container>
<!--<pre>{{control.errors | json}}</pre>-->
</div>
</ng-template>
import {
Component,
Input,
ViewChild,
ViewContainerRef,
OnDestroy,
} from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { OnInit } from '@angular/core/core';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-field-error-display',
templateUrl: './field-error-display.component.html'
})
export class FieldErrorDisplayComponent implements OnInit, OnDestroy {
@ViewChild('template', { static: true })
template;
@Input() control: AbstractControl;
subscription: Subscription;
uncoveredError: string[] = [];
constructor(private readonly viewContainerRef: ViewContainerRef) {}
ngOnInit() {
// these errors have messages in the html so we'll ignore them here. The main purpose of this section is to get
// errors like "unique: 'Email is in use'" so we can display the text to the user.
const blackList = [
'zeroValue',
'required',
'zeroValue',
'maxlength',
'Mask error',
'email',
'dateLessThan',
'typeahead',
];
this.subscription = this.control.statusChanges.subscribe((status) => {
this.uncoveredError = [];
if (status === 'VALID') {
return;
}
Object.keys(this.control.errors || []).forEach((error) => {
if (blackList.indexOf(error) > 0) {
return;
}
const errorValue = this.control.errors[error];
// ReSharper disable once TypeGuardProducesNeverType
if (typeof errorValue === 'string') {
this.uncoveredError.push(errorValue);
}
});
});
this.viewContainerRef.createEmbeddedView(this.template);
}
ngOnDestroy(): void {
try {
this.subscription.unsubscribe();
} catch (e) {}
}
}
<input [formControl]="control"/>
<app-field-error-display [control]="control"></app-field-error-display>
import {
FormGroup,
FormControl,
ValidatorFn,
AbstractControl,
ValidationErrors,
} from '@angular/forms';
export class ValidationExtensions {
static validateAll(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach((field) => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAll(control);
}
});
formGroup.markAllAsTouched();
}
static fieldsMatch(left: string, right: string): ValidatorFn {
return (formGroup: AbstractControl): ValidationErrors | null => {
const rightControl = formGroup.get(right);
const leftControl = formGroup.get(left);
const leftLabel = ValidationExtensions.getControlLabel(left);
const rightLabel = ValidationExtensions.getControlLabel(right);
const error = {
mismatch: `Both ${leftLabel} and ${rightLabel} must match.`,
};
if (
(!rightControl || !rightControl.value) &&
leftControl &&
leftControl.value
) {
return error;
}
if (leftControl.value !== rightControl.value) {
rightControl.setErrors(error);
return error;
}
return null;
};
}
static decimalValidator(places: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) return null;
const pattern = `^\\d*\\.{0,1}\\d{0,${places}}$`;
if (!new RegExp(pattern).test(control.value)) {
return {
decimal:
'Value must be either a whole number (e.g. 12) or a decimal (e.g. 12.34)',
};
}
};
}
static passwordValidation(password: string): PasswordValidationResult {
const digitPattern = /(\D*\d)/;
const lowercasePattern = /^([^a-z]*[a-z])/;
const uppercasePattern = /^([^A-Z]*[A-Z])/;
return new PasswordValidationResult(
digitPattern.test(password),
lowercasePattern.test(password),
uppercasePattern.test(password),
password.length >= 8 && password.length <= 100
);
}
static conditionalValidator(predicate, validator): ValidatorFn {
return (formControl) => {
if (!formControl.parent) {
return null;
}
if (predicate()) {
return validator(formControl);
}
return null;
};
}
static errorMatrix(errors: ValidationErrors, label = ''): string[] {
if (!errors) {
return [];
}
if (label) {
label += ' ';
}
const messages: string[] = [];
if (errors.required || errors.zeroValue) {
messages.push(`${label}Field is required.`);
}
if (errors.maxlength) {
messages.push(
`${label}Field has a max length of ${errors.maxlength.requiredLength}`
);
}
if (errors['Mask error'] || errors.pattern || errors['pattern']) {
messages.push(`${label}Input not in correct format.`);
}
if (errors.email || errors.email) {
messages.push(`${label}Please enter a valid email.`);
}
if (errors.typeahead) {
messages.push(`${label}Field is required.`);
}
if (errors.mismatch) {
messages.push(`${label}Field does not match.`);
}
if (errors.pattern) {
messages.push(`${label}Field doesn't match requirements.`);
}
return messages;
}
static getControlLabel(controlName: string): string {
const nativeElement = document.getElementById(controlName);
if (nativeElement) {
const placeholder = nativeElement.getAttribute('placeholder');
const label = nativeElement.parentNode.querySelector(
`label[for="${controlName}"]`
);
if (label) {
return label.textContent.trim();
} else {
return placeholder;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment