Issue
i am using Angular8 with bootstrap, i have made inputs to be from a loop array. If there is any validation error for email or fax number, it is throwing validation error, then if there is error for email in the first row, if there is any error for fax in second row it shows, if in the 3rd row if email is entered wrongly and made correct, the first row validation error also goes off. But i want to show validation error for all rows of there is an error with respect to fax or email respectively.
HTML:
<input type="text" class="form-control" placeholder="Email" name="Recepient"
formControlName="recipients" *ngIf="data.value.deliveryMethodId == 178"
(focusout)="validationErrorOnFocusOut('emailvalid',data)"
[ngClass]="{ 'is-invalid': emailsubmitted && data.controls.recipients.errors}"
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$" autocomplete="off">
<div *ngIf="(emailsubmitted && data.controls.recipients.errors)" class="invalid-feedback">
<div *ngIf="(emailsubmitted && data.controls.recipients.errors)">
Please enter valid email</div>
</div>
<input type='text' prefix="+1 " mask=" (000) 000-0000" class="form-control"
placeholder="Recepient" name="Recepient" formControlName="recipients"
*ngIf="data.value.deliveryMethodId == 179" maxLength="18"
(focusout)="validationErrorOnFocusOut('fax',data)"
autocomplete="off"
[ngClass]="{ 'is-invalid':faxsubmitted && data.controls.recipients.errors.mask}">
<div *ngIf="faxsubmitted && data.controls.recipients.errors.mask" class="invalid-feedback">
<div *ngIf="faxsubmitted && data.controls.recipients.errors.mask">Please enter valid fax number
</div>
</div>
TS:
public validationErrorOnFocusOut(name, data) {
if (name == "emailvalid") {
if (data.controls.recipients.status === "VALID") {
this.emailsubmitted = false;
} else {
this.emailsubmitted = true;
}
if (
data.controls.recipients.value === "" ||
data.controls.recipients.value === null
) {
this.emailsubmitted = false;
}
} else if (name == "fax") {
if (data.controls.recipients.status === "VALID") {
this.faxsubmitted = false;
} else {
this.faxsubmitted = true;
}
if (
data.controls.recipients.value === "" ||
data.controls.recipients.value === null
) {
this.faxsubmitted = false;
}
}
}
Solution
You have set up your form correctly using the FormBuilder
. My Solution will focus on the Reactivity
of your form
Below are the steps I have taken
- Remove the binding on
[disabled]
property https://stackoverflow.com/a/58831653/13680115
Below is a warning in the console thrown if you do include[disabled]
property
It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.
Example:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
- Remove the
onChange
event handler. In its place we will have below in ourngOninit
function. We will watch for form valuechanges and react to this to disable the appropriate control
this.printListControl.valueChanges
.pipe(
distinctUntilChanged(),
tap((controls: any[]) => {
controls.forEach(({ mail, deliveryMethodId }, index) => {
const control = this.printListControl.at(index);
if (mail) {
control.get("deliveryMethodId").enable({ emitEvent: false });
control.get("recipients").enable({ emitEvent: false });
} else {
// I would not include below two lines for better user experience.
control
.get("deliveryMethodId")
.setValue(null, { emitEvent: false });
control.get("recipients").setValue(null, { emitEvent: false });
control.get("deliveryMethodId").disable({ emitEvent: false });
control.get("recipients").disable({ emitEvent: false });
}
// console.log(deliveryMethodId);
control
.get("recipients")
.setValidators(this.getRecipientsValidation(deliveryMethodId));
});
})
)
.subscribe();
}
getRecipientsValidation(deliveryMethodId) {
return +deliveryMethodId === 178
? [Validators.pattern(this.emailPattern), Validators.required]
: +deliveryMethodId === 179
? [
Validators.minLength(10),
(10),
Validators.required
]
: [Validators.required];
}
Now whenever the mail
value is changed the deliveryMethodId
and recipients
control are enabled or disabled
Also we update the validators based on the delivery method selected
- Remove the pattern validations from the html. We will use
Validators.pattern
We can declare the patterns
emailPattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
-
Remove the event for
validationErrorOnFocusOut
from the template -
Since we have removed the template validation, we can employ the use of
Validator
static classes
let printList = this.printListArray.map(x => {
const { deliveryMethodId } = x;
return this.fb.group({
id: x.id,
name: x.name,
mail: x.mail,
electronics: x.electronics,
deliveryMethodId: x.deliveryMethodId,
recipients: [
x.recipients,
{
validators:
deliveryMethodId === 178
? [Validators.pattern(this.emailPattern), Validators.required]
: deliveryMethodId === 179
? [
Validators.minLength(10),
Validators.maxLength(10),
Validators.required
]
: [Validators.required],
updateOn: "blur"
}
]
});
});
We are applying validation to each control individally to avoid their effect on other controls
- Finally our html will be something like this
<div class="custom-control custom-checkbox"
*ngFor="let data of exampleForm.get('printList').controls; let j = index" formArrayName="printList">
<div [formGroupName]="j">
<table class="table table-print table-borderless">
<tbody>
<tr>
<td scope="row" class="width90">{{data.value.name}}</td>
<td class="width50">
<input
type="checkbox"
name="mail"
formControlName="mail"
/>
</td>
<td class="width50">
<input
type="checkbox"
name="electronics"
formControlName="electronics"
/>
</td>
<td class="width100">
<select
class="custom-select"
formControlName="deliveryMethodId"
name="deliveryMethodId"
tabindex="1" (change)="dropdownSelection(j)"
>
<option value=null>Select One </option>
<option
*ngFor="let agencyType of DeliveryMethod"
[value]="agencyType.id"
>
{{agencyType.label}}</option
>
</select>
</td>
<td class="width200">
<ng-container *ngIf="data.value.deliveryMethodId == 178">
<input type="text" class="form-control" placeholder="Email" name="Recepient"
formControlName="recipients" *ngIf="data.value.deliveryMethodId == 178"
[ngClass]="{ 'is-invalid': data.get('recipients').invalid && data.get('recipients').touched }"
autocomplete="off">
<div class='invalid-feedback' *ngIf="data.get('recipients').invalid">
Please enter valid email
</div>
</ng-container>
<ng-container *ngIf="data.value.deliveryMethodId == 179">
<input type="text" prefix="+1 " class="form-control" placeholder="(###) ### - ####"
formControlName="recipients" name="recipients" autocomplete="off"
*ngIf="data.value.deliveryMethodId == 179"
mask=" (000) 000-0000" [showMaskTyped]="false"
[ngClass]="{ 'is-invalid' : data.get('recipients').invalid && data.get('recipients').touched }" >
<div class='invalid-feedback'>
Please enter valid fax number
</div>
</ng-container>
<ng-container *ngIf="data.value.deliveryMethodId != '178' && data.value.deliveryMethodId != '179'">
<input type="text" class="form-control" placeholder="Recepient" name="Recepient"
[ngClass]="{ 'is-invalid' : data.get('recipients').invalid && data.get('recipients').touched }"
formControlName="recipients"
/>
<div class='invalid-feedback'>
Field is required
</div>
</ng-container>
</td>
</tr>
</tbody>
</table>
</div>
</div>