TODO
Last active
December 2, 2021 19:29
-
-
Save Eonasdan/02fcb339dada46c64246a882e39e7f14 to your computer and use it in GitHub Desktop.
Angular bootstrap control display error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) {} | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <input [formControl]="control"/> | |
| <app-field-error-display [control]="control"></app-field-error-display> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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