Email Validation with required overlaps in Angular Reactive Forms - javascript

Using Angular Reactive Forms for validation of Email.
I added Validators Required and Validators Email but Its displaying both as shown in below image. I just want one Error to be displayed at a time.
HTML Code :
<form [formGroup]="NamFomNgs">
<label>Email :
<input type="email" name="MylHtm" formControlName="MylNgs">
</label><br>
<div class="ErrMsgCls" *ngIf="(NamFomNgs.controls['MylNgs'].touched || NamFomNgs.controls['MylNgs'].dirty) &&
!NamFomNgs.controls['MylNgs'].valid">
<span *ngIf="NamFomNgs.controls['MylNgs'].errors.required">This field is required</span>
<span *ngIf="NamFomNgs.controls['MylNgs'].errors.email">Enter valid email</span>
</div><br>
<button [disabled]="!NamFomNgs.valid">Submit</button>
</form>
Typescript Code :
NamFomNgs:FormGroup;
constructor(private NavPkjVaj: ActivatedRoute, private HtpCncMgrVaj: HttpClient,private FomNgsPkjVaj: FormBuilder)
{
this.NamFomNgs = FomNgsPkjVaj.group(
{
MylNgs:[null,Validators.compose([
Validators.required,
Validators.email ])]
});
}
I feel it's a bug in angular form.

You can create common component to display validation message:
custom-validation-with-error-message
HTML:
<div *ngIf="errorMessage !== null">
{{errorMessage}}
</div>
constrol-message.component.ts:
import { OnInit } from '#angular/core';
import { Component, Input } from '#angular/core';
import { FormGroup, FormControl, AbstractControl } from '#angular/forms';
import { CustomValidationService } from '../custom-validation.service'
export class ControlMessageComponent implements OnInit {
ngOnInit() {
}
#Input() control: FormControl;
constructor() { }
/**
* This method is use to return validation errors
*/
get errorMessage() {
for (let propertyName in this.control.errors) {
if (this.control.errors.hasOwnProperty(propertyName) && this.control.touched) {
return CustomValidationService.getValidatorErrorMessage(this.getName(this.control), propertyName, this.control.errors[propertyName]);
}
if (this.control.valueChanges) {
return CustomValidationService.showValidatorErrorMessage(propertyName, this.control.errors[propertyName])
}
}
return null;
}
/**
* This method used to find the control name
* #param control - AbstractControl
*/
private getName(control: AbstractControl): string | null {
let group = <FormGroup>control.parent;
if (!group) {
return null;
}
let name: string;
Object.keys(group.controls).forEach(key => {
let childControl = group.get(key);
if (childControl !== control) {
return;
}
name = key;
});
return name;
}
}

Related

Consistent updating of validation state in a custom reactive form control?

I'm trying to create a material username reactive form control using the approach outlined in this tutorial.
The end result should work in a form like this ( the fs-username-form is the custom reactive form component ):
<mat-card class="UsernameFormTestCard">
<form class="UsernameForm" [formGroup]="form" (ngSubmit)="submit()">
<mat-form-field>
<input
matInput
placeholder="First name"
type="text"
formControlName="firstName"
/>
<mat-hint *ngIf="!username">Example Monica</mat-hint>
<mat-error>Please enter your first name</mat-error>
</mat-form-field>
<fs-username-form formControlName="username"></fs-username-form>
<button mat-button type="submit" [disabled]="!form.valid">Submit</button>
</form>
</mat-card>
If the form is valid then the Submit button enables.
And it works ... sort of ... It enables reactively ... but not consistently. The minimum number of characters is set to 4. And the submit button enables when the length is 4. However if we start removing characters from the username the submit button only disables after the length of the username is 2.
This is the Stackblitz demo showing this.
The username-form.component.ts is implemented like this:
import { Component } from '#angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators,
} from '#angular/forms';
#Component({
selector: 'fs-username-form',
templateUrl: './username-form.component.html',
styleUrls: ['./username-form.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: UsernameFormComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: UsernameFormComponent,
},
],
})
export class UsernameFormComponent implements ControlValueAccessor, Validator {
//=============================================
// ControlValueAccessor API Methods
//=============================================
disabled: boolean = false;
// Dummy initialization.
//The implementation is passed in
// with registerOnChange.
onChange = (username: string) => {};
onTouched = () => {};
touched = false;
usernameValue = '';
writeValue(username: any): void {
this.usernameValue = username;
}
//=============================================
// Registration API Methods
//=============================================
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
registerOnTouched(onTouched: any): void {
this.onTouched = onTouched;
}
markAsTouched() {
if (!this.touched) {
this.onTouched();
this.touched = true;
}
}
setDisabledState(disabled: boolean) {
this.disabled = disabled;
if (disabled) {
this.usernameControl?.disable();
} else {
this.usernameControl?.enable();
}
}
//=============================================
// Validator API Methods
//=============================================
validate(control: AbstractControl): ValidationErrors | null {
console.log('VALIDATE IS GETTING CALLED');
console.log('Is the form valid: ', this.usernameForm.valid);
if (this.usernameForm.valid) {
return null;
}
let errors: any = {};
errors = this.addControlErrors(errors, 'username');
return errors;
}
addControlErrors(allErrors: any, controlName: string) {
const errors = { ...allErrors };
const controlErrors = this.usernameForm.controls[controlName].errors;
if (controlErrors) {
errors[controlName] = controlErrors;
}
return errors;
}
/**
* Registers a call to onChange to inform
* parent forms of valueChanges events.
*/
constructor() {
this.usernameControl?.valueChanges.subscribe((u) => {
this.onChange(u);
});
}
public usernameForm: FormGroup = new FormGroup({
username: new FormControl('', [
Validators.required,
Validators.minLength(4),
]),
});
get username() {
return this.usernameForm.get('username')?.value;
}
get usernameControl() {
return this.usernameForm.get('username');
}
}
The username-form.component.html template looks like this:
<form class="UsernameForm" [formGroup]="usernameForm">
<mat-form-field>
<input
matInput
placeholder="username"
type="text"
formControlName="username"
/>
<mat-hint *ngIf="!username">Example Monica</mat-hint>
<mat-error>Please enter your username</mat-error>
</mat-form-field>
</form>
As can be seen it implements the ControlValueAccessor and Validator interfaces.
Call on onChange are made when the user types in the username field and, if I understand correctly, this in turn should cause the parent form to call validate on the username control.
Why does the submit button in the demo form not update consistently or why the username-form component does not?
Got it working. Instead of checking whether the form containing the control is valid in the validate method, I switched it to check whether the control itself is valid like this:
//=============================================
// Validator API Methods
//=============================================
validate(control: AbstractControl): ValidationErrors | null {
if (this.usernameControl.valid) {
return null;
}
let errors: any = {};
errors = this.addControlErrors(errors, 'username');
return errors;
}
And now it works. This is a new working demo.

Form control missing randomly for reactive form with filter pipe

Below is a simple reactive form with the filter of an array of checkboxes.
As soon as page render getting error
Cannot find control with path: 'accountsArray -> 555'
However, the filter is working perfectly, but while removing any character from filter throws an error
Cannot find control with path: 'accountsArray -> 123'
Form control not found based on search.
Below is length code, but that will help you to understand clearly.
Component:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormArray, FormGroup, FormControl } from '#angular/forms';
import { SubAccount } from './account-model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
searchForm: FormGroup;
searchTerm = '';
formUpdated = false;
accounts = [
new SubAccount('123'),
new SubAccount('555'),
new SubAccount('123555')
];
subAccount = [];
constructor(private fb: FormBuilder) { }
get accountsArray(): FormArray {
return this.searchForm.get('accountsArray') as FormArray;
}
addAccount(theAccount: SubAccount) {
this.accountsArray.push(this.fb.group({
account: theAccount
}));
}
ngOnInit() {
this.formUpdated = false;
this.searchForm = this.fb.group({
accountSearch: '',
accountsArray: this.fb.array([new FormControl('')])
});
this.accounts.forEach((field: any) => {
this.subAccount.push({ key: field.key, value: field.key });
});
const fieldFGs = this.subAccount.map((field) => {
const obj = {};
if (field.value) {
obj[field.value] = true;
} else {
obj[field] = true;
}
return this.fb.group(obj);
});
const fa = this.fb.array(fieldFGs);
this.searchForm.setControl('accountsArray', fa);
this.formUpdated = true;
}
getAccountNumber(account: SubAccount) {
return Object.keys(account)[0];
}
}
View:
<div [formGroup]="searchForm" *ngIf="formUpdated">
<label for="search">Find an account...</label>
<input id="search" formControlName="accountSearch" [(ngModel)]="searchTerm" />
<div formArrayName="accountsArray" *ngIf="formUpdated">
<div *ngFor="let account of accountsArray.controls | filter: 'key' :searchTerm; let ind=index">
<input type="checkbox" id="checkbox_claim_debtor_{{ind}}" formControlName="{{getAccountNumber(account.controls)}}"/>
<span> {{getAccountNumber(account.controls)}} </span>
</div>
</div>
</div>
Pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], field: string, value: string): any[] {
if (!value && !items) {
return items;
}
return items.filter((item) => {
const val = Object.keys(item.controls)[0];
if (val && val.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
return true
} else {
return false;
}
});
}
}
Appreciate your help.
Stackblitz link:
https://stackblitz.com/edit/angular-9ouyqr
please check the [formGroupName]="ind" , it is not written while iterating the form array ,formGroupname should be addeed with the form index
<div [formGroup]="searchForm" *ngIf="formUpdated">
<label for="search">Find an account...</label>
<input id="search" formControlName="accountSearch" [(ngModel)]="searchTerm" />
<div formArrayName="accountsArray" *ngIf="formUpdated">
<div [formGroupName]="ind" *ngFor="let account of accountsArray.controls | filter: 'key' :searchTerm; let ind=index"
>
<input type="checkbox" id="checkbox_claim_debtor_{{ind}}" formControlName="{{getAccountNumber(account.controls)}}"/>
<span> {{getAccountNumber(account.controls)}} </span>
</div>
</div>
</div>

Angular 6 Required only one field from many fields Reactive Form

I am new in angular. I have one scenario where I need only one field required from 5 fields in the form, means if the user fills at least one field then form makes valid.
Thanks in advance.
Since you need to check for the validity of whole form only if one of the fields is non empty , You can manually set the validity like below :
if(!this.valid){
this.form.setErrors({ 'invalid': true});
}else{
this.form.setErrors(null);
}
Where this.valid is your condition based on which you can set the validity
You can check the example : https://angular-exmphk.stackblitz.io
You can also check the answer : FormGroup validation in "exclusive or" which does form validation based on some condition
Hope this helps
See Custom Validators and Cross-field validation in https://angular.io/guide/form-validation
Exact example from our app, where at least one phone number field must be entered. This is a Validator Function, i.e. implements https://angular.io/api/forms/ValidatorFn
import { AbstractControl } from "#angular/forms";
import { Member } from "../../members/member";
export function phone(control: AbstractControl): {[key: string]: any} {
if (!control.parent) return;
const form = control.parent;
const member: Member = form.value;
if (member.contactphone || member.mobile || member.contactphonesecond) {
[
form.controls['contactphone'],
form.controls['mobile'],
form.controls['contactphonesecond']
].forEach(control => {
control.setErrors(null);
});
} else return {'noPhone': 'None of contact phones is specified'};
}
Member is our class that defines all the form fields, your code will be different but the example of the custom validator should help.
Check this example of phone number validator
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { NumberValidator } from '../validators/form-validators';
constructor(
private fb: FormBuilder){}
FormName: FormGroup = this.fb.group({
phoneNumber: ['', NumberValidator]
});
in form-validator file
import { AbstractControl, ValidatorFn } from '#angular/forms';
export const NumberValidator = (): ValidatorFn => {
return (control: AbstractControl): { [key: string]: any } | null => {
const mobileRegex = /^[()-\d\s]*$/g;
const result = mobileRegex.test(control.value);
return result ? null : { mobileInvalid: { value: control.value } };
};
};
let me know if you have any doubts.
<form [formGroup]="formdata">
<div class="form-group">
<label for="fieldlabel1">fieldLabel1</label>
<input type="text" id="fieldlabel1" formControlName="fieldName1" class="form-control"><br>
<label for="fieldlabel2">fieldLabel2</label>
<input type="text" id="fieldlabel2" formControlName="fieldName2" class="form-control">
</div>
</form>
<div class="row">
<div class="col-sm-12">
<button type="submit" value="submit" (click)="somefunctioncall()" [disabled]="formdata.invalid">
Submit
</button>
</div>
</div>
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { OnInit } from '#angular/core';
export class test {
formdata: FormGroup;
ngOnInit() {
this.formdata = new FormGroup({
fieldName1: new FormControl("", Validators.compose([
Validators.required
])),
fieldName2: new FormControl("", Validators.compose([
// remove required validation for one you dont need.
]))
})
}
}

Custom validator is not calling for each key press in Angular 2

I wrote a custom validator for Reactive forms in Angular 2.But my function is validating only first key press in the text field. Here my custom function supposed to validate each key press. Could any one please correct me.
This is the way I am calling custom function in my class.
'customerNumberOwner': new FormControl('', [CustomValidators.CustomerNumberCustomValidation(6,8)]),
Here is my custom function.
//Custom validator for Customer number owner
static CustomerNumberCustomValidation(min: number, max: number): ValidatorFn {
return (c: AbstractControl): { [key: string]: boolean } | null => {
var reg=/[^A-Za-z0-9]+/;
if(c && (c.value !== '')){
const str=c.value;
if (str.match(reg) || str.length<min ||str.length>max ){
console.log('Invalid')
return {
'CustomerNumberCustomValidation' : true
};
}
}
return null;
};
}
I hope this will help
DEMO
HTML:
<h1>
Try Reactive Form Validation with custom validation
</h1>
<form [formGroup]="basicForm">
<input type="text" minlength="10" maxlength="10" formControlName="name" placeholder="Enter Name For Validation" />
<p *ngIf="basicForm.get('name').hasError('required') && basicForm.get('name').touched">Required</p>
<p *ngIf="basicForm.get('name').hasError('minlength')">Min Length 10</p>
<p *ngIf="basicForm.get('name').hasError('maxlength')">Max Length 10</p>
<p *ngIf="basicForm.get('name').hasError('CustomerNumberCustomValidation')">Pattern Invalid /[^A-Za-z0-9]+/</p>
</form>
app.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { CustomValidatorService } from './custom-validator.service'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
basicForm: FormGroup;
ngOnInit() {
this.createForm();
}
constructor(private fb: FormBuilder) {
}
createForm() {
this.basicForm = this.fb.group({
name: this.fb.control(null, [Validators.required, Validators.minLength(10), CustomValidatorService.CustomerNumberCustomValidation])
})
}
}
custom-validator.service.ts:
import { Injectable } from '#angular/core';
import { AbstractControl, FormControl, ValidatorFn } from '#angular/forms';
#Injectable()
export class CustomValidatorService {
constructor() { }
static CustomerNumberCustomValidation(control: FormControl) {
var reg = /[^A-Za-z0-9]+/;
if (control.value) {
const matches = control.value.match(reg);
return matches ? null : { 'CustomerNumberCustomValidation': true };
} else {
return null;
}
}
}

Angular 5 - Validating own input components derived from a base componet

Currently we are going to port our old application to new technologies and get rid of old design failures and improve the overall UX.
At the moment we are wrapping all standard inputs in own components, so we can change them very easily if needed. All our input components could have a validation but the input components can be nested in an input group of bootstrap, like this:
<form #myForm="ngForm">
<div>
<label>Birth Year</label>
<vng-controls-textbox #user="ngModel" name="user" minlength="5" ngModel ></vng-controls-textbox>
<label>Text</label>
<vng-controls-textbox #text="ngModel" name="text" maxlength="5" ngModel ></vng-controls-textbox>
</div>
<div vng-control-group [icon]="['fa','user']">
<vng-controls-textbox required type="text" #username="ngModel" ngModel name="Username">
</vng-controls-textbox>
<vng-controls-textbox required type="password" #password="ngModel" ngModel name="Passwort">
</vng-controls-textbox>
</div>
</form>
The validation works fine for standalone input components, but we want to deactivate the validation for each nested component in our and get all errors of all possible controls with validations. The desired solution should be a generic approach and no specifics, if possible.
Our base component:
import { Component, OnInit, EventEmitter, Input, Output, forwardRef, AfterViewInit, Self } from '#angular/core';
import { Validator, ControlValueAccessor, FormControl, NgControl } from '#angular/forms';
#Component({
selector: 'app-base'
})
export class BaseComponent implements OnInit, ControlValueAccessor, Validator, AfterViewInit//, IControlBase<any>
{
public ngControl: any;
private parseError: boolean;
private _value: any = '';
/** Input value */
#Input() set value(newValue: any) {
if (newValue != this._value && newValue !== undefined) {
console.log("[BaseComponent]: Set value " + newValue);
this.valueChange.emit(newValue);
this._value = newValue;
}
};
get value(): any {
if (this._value) {
return this._value;
}
}
/** Platzhalter */
#Input() placeholder: any;
/** Disabled */
#Input() disabled: boolean;
/** Name des Controls */
#Input() name: string;
/** Optional: Typ des Controls (Text, Password) */
#Input() type?: string;
#Input() hideErrors: boolean = false;
#Output() valueChange: EventEmitter<any> = new EventEmitter<any>();
// this is the initial value set to the component
public writeValue(obj: any) {
if (obj !== undefined && obj) {
console.log("[BaseComponent] writeValue ", obj)
this._value = obj;
}
}
// registers 'fn' that will be fired wheb changes are made
// this is how we emit the changes back to the form
public registerOnChange(fn: any) {
this.propagateChange = fn;
}
// validates the form, returns null when valid else the validation object
// in this case we're checking if the json parsing has passed or failed from the onChange method
public validate(c: FormControl) {
return null;
}
// not used, used for touch input
public registerOnTouched() { }
// change events from the textarea
private onChange(event) {
if (event.target.value !== undefined) {
console.log("[BaseComponent] "+this.name+" OnChange " + event.target.value)
// get value from text area
this._value = event.target.value;
this.propagateChange(this._value);
}
}
// the method set in registerOnChange to emit changes back to the form
private propagateChange = (_: any) => { };
registerOnValidatorChange?(fn: () => void): void {
console.log("[BaseComponent]: registerOnValidatorChange");
}
setDisabledState?(isDisabled: boolean): void {
console.log("[BaseComponent]: setDisabledState");
}
constructor(#Self() ngControl: NgControl) {
ngControl.valueAccessor = this;
if (ngControl) {
this.ngControl = ngControl;
}
}
ngOnInit() {
}
ngAfterViewInit(): void {
//debugger;
//this.placeholder = this.translateService.instant((this.placeholder ? this.placeholder : this.name))
}
}
Our control group component typescript:
import { Component, OnInit, Input, Host, Self } from '#angular/core';
import { ControlContainer, FormControl, AbstractControl, NgControl } from '#angular/forms';
import { BaseComponent } from '../../_bases/base.component'
import { QueryList, ViewChildren, ContentChildren, TemplateRef, ContentChild } from '#angular/core';
import { TextboxComponent } from '../textbox/textbox.component';
/**
* Komponente für Input-Groups
* #example
* <div vng-control-group [config]="textboxUsernameConfig">
* // Kann ein diverses Control sein, was als CSS-Klasse "form-control besitzt"
* <input class="input form-control" />
* </div>
*
* // oder
*
* <div vng-control-group [icon]="['fa','user']" [label]="Test Label" [position]="'append'">
*/
#Component({
selector: 'div[vng-control-group]',
templateUrl: './control-group.component.html'
})
export class ControlGroupComponent {
private controlContainer: ControlContainer;
public formControl: AbstractControl;
#ContentChild(TemplateRef) template: TemplateRef<any>;
ngAfterContentInit() {
}
/** Konfiguration der Inputgroup als Objekt */
#Input() config: InputGroupConfig;
/** Icon als Attribut (z.B. im HTML) */
#Input() icon?: object;
/** Label als Attribut (z.B. im HTML) */
#Input() label?: string;
/** Position als Attribut (z.B. im HTML) */
#Input() position?: GroupPosition;
constructor(#Host() parent: ControlContainer) {
//this.controlContainer = parent;
//this.formControl = this.controlContainer.control;
}
ngOnInit() {
// Wenn kein Konfig-Objekt übergeben worden ist, setze die Attribute bzw. setze Default-Werte
if (!this.config) {
console.log("[ControlGroupComponent]: Keine Config übergeben")
this.config = {
icon: this.icon || ['fa', 'question'],
label: this.label || '',
position: this.position || GroupPosition.prepend
}
}
}
}
export interface InputGroupConfig {
icon?: object,
label?: string,
position?: GroupPosition
}
export enum GroupPosition {
append = 'append',
prepend = 'prepend'
}
Our control-group html:
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">#</span>
</div>
<ng-content></ng-content>
</div>
Show error component (ts/html):
// show-errors.component.ts
import { Component, Input } from '#angular/core';
import { AbstractControlDirective, AbstractControl } from '#angular/forms';
#Component({
selector: 'show-errors',
template: `
<ul *ngIf="shouldShowErrors()">
<li style="color: red" *ngFor="let error of listOfErrors()">{{error}}</li>
</ul>
`,
})
export class ShowErrorsComponent {
private static readonly errorMessages = {
'required': () => 'This field is required',
'minlength': (params) => 'The min number of characters is ' + params.requiredLength,
'maxlength': (params) => 'The max allowed number of characters is ' + params.requiredLength,
'pattern': (params) => 'The required pattern is: ' + params.requiredPattern,
'years': (params) => params.message,
'countryCity': (params) => params.message,
'uniqueName': (params) => params.message,
'telephoneNumbers': (params) => params.message,
'telephoneNumber': (params) => params.message
};
#Input()
private control: AbstractControlDirective | AbstractControl;
shouldShowErrors(): boolean {
return this.control &&
this.control.errors &&
(this.control.dirty || this.control.touched);
}
listOfErrors(): string[] {
return Object.keys(this.control.errors)
.map(field => this.getMessage(field, this.control.errors[field]));
}
private getMessage(type: string, params: any) {
return ShowErrorsComponent.errorMessages[type](params);
}
}
How can we achieve that? I'm pretty stuck since 3 days and can't get it working.I really don't know how to disable the error messages in the content of and print all errors messages of all nested inputs under the input group.
Besides that, our nested inputs within the control group are looking pretty bad:
Bad bootstrap format on nested inputs
I've created a stackblitz example which demonstrates the behavior of both problems:
https://angular-ng-bootstrap-khjkkhhjkhk.stackblitz.io
Editor:
https://stackblitz.com/edit/angular-ng-bootstrap-khjkkhhjkhk
Do you have any idea how can we fix that too? I guess it's because of the which wraps the controls in an additional div which causes the strange looking inputs. Preferably we want to style them in the manner of bootstrap.
Maybe it's not the case but I see you're not declaring the providers in #Component decorator using ControlValueAccessor.
Like this:
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => InputComponent), multi: true }
]
})

Categories