"How can I display validation errors for email and fax in each row of loop items using Angular? Specifically working with Angular 8

In my Angular8 project with Bootstrap, I have created input fields dynamically from an array using a loop. When there is a validation error for the email or fax number input, the error is displayed. However, I noticed that if there is an error in the first row's email field and then an error in the second row's fax field, only the second error is shown. Additionally, if the email field in the third row is initially incorrect but then corrected, the validation error for the first row also disappears.

My objective is to display validation errors for all rows if there is an issue with either the fax or email field in any of them.

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;
      }
    }
  }

DEMO

Answer №1

Congratulations on setting up your form using the FormBuilder. I will be focusing on enhancing the reactivity of your form in my solution.

Here are the steps I have implemented:

  1. Eliminated the binding on the [disabled] property as suggested in this Stack Overflow post. The console displays a warning when including the [disabled] property, advising to set 'disabled' to true during control setup in the component class to avoid errors.

When utilizing the disabled attribute with a reactive form directive, ensure to set disabled to true
during control setup in your component class to prevent 'changed after checked' errors.

Example:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
  1. Removed the onChange event handler and replaced it with logic within our ngOnInit function. We now monitor form value changes and dynamically enable or disable the appropriate controls based on the values.
      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 {
              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 });
            }

            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, changes in the mail value trigger dynamic enabling/disabling of the deliveryMethodId and recipients controls. Additionally, validators are adjusted based on the selected delivery method.

  1. Excluded pattern validations from the HTML markup in favor of utilizing Validators.pattern.

We can define the patterns as follows:

emailPattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
  1. Disregarded the validationErrorOnFocusOut event from the template, replacing it with static classes from Validator

    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"
          }
        ]
      });
    });

Applying validation individually to each control prevents interference with other controls.

  1. Finally, here's an updated snippet of the HTML structure:
<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>

Explore the DEMO HERE

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the method for retrieving the XMLHttpRequest errors registered with addEventListener?

I am struggling to find a solution. https://i.stack.imgur.com/bRJho.gif ...

What is the best way to make the first option blank?

Is there a way to have the select field start with an empty value instead of the default dollar sign? The demonstration can be found at https://codesandbox.io/s/material-demo-forked-xlcji. Your assistance in achieving this customization would be greatly a ...

Using es6 map to deconstruct an array inside an object and returning it

I am looking to optimize my code by returning a deconstructed array that only contains individual elements instead of nested arrays. const data = [ { title: 'amsterdam', components: [ { id: 1, name: 'yanick&a ...

How can I create a drop-down list in Bootstrap 4 input?

Displayed below is an example of a customized button dropdown featuring material icons and the Google Roboto Mono font, ideal for optimal spacing. My inquiry pertains to replicating this behavior with an input field. <!-- Bootstrap CSS --> < ...

Ways to prevent images from vanishing when the mouse is swiftly glided over them

Within the code snippet, the icons are represented as images that tend to disappear when the mouse is swiftly moved over them. This issue arises due to the inclusion of a transition property that reduces the brightness of the image on hover. However, when ...

What is the best method for substituting a null searchPhrase variable?

I'm facing an issue with my code. I have implemented features to sort, filter, and search data. However, when the search textarea is left empty (null), I encounter the following error: Error: error https://localhost/cases/caselistsorted/no-filter/sea ...

Ways to loop through individual characters within a string

Is there a way to iterate through a string using the *ngFor? I have a string that contains binary code (e.g. 0010) and depending on each bit, I need to display a different icon. <div class="row" *ngFor="let item of subscribedCommandBus2Easy; let i = in ...

Unfortunately, despite attempting to kill all processes linked to port 4200, it seems that it is still in use

Getting angular up and running on my Mac OS X 10.11.3 El Capitan has been quite a challenge. After installing nodeJS and npm, I used npm to install angular-cli. To create a new app, I executed the command sudo ng new first-app Then, I navigated into the ...

Encountering errors when trying to render views in Node/Express is a common

I have set up my nodejs/express application like this; app.set('views', __dirname + './views'); app.set('view engine', 'ejs'); After that, I created a route as shown below app.get('/page/MyPage', functio ...

Function in Angular that provides the elementId when hovering over it

Currently, I am working on an angular project that involves creating a wiki window. Basically, when any element is hovered over with the mouse, its definition will appear inside the wiki window. I am wondering if it would be possible to create a global fun ...

Countdown timer options for Ionic and Angular 2

I am in need of a simple Angular 2 (4) / Ionic 2 Countdown timer for my project, but unfortunately I haven't been able to come across any open-source options. That's why I'm turning to you all for suggestions. Here is an example of what I h ...

The parameter of type 'X' cannot be assigned to the argument of type 'Y'

I've defined the following types: export type Optional<T> = T | null; along with this function updateBook( book: Array<Optional<Hostel>>) which I'm using like this let book: Hostel | null [] = [null]; updateBook(book) Ho ...

The specified type '{ flag: boolean; }' cannot be assigned to the type 'IntrinsicAttributes & boolean'

Just delving into TypeScript and I've hit a snag when trying to pass an element to another component. Feeling lost on how to proceed. Error lurking in Cards component export default function MySpecialProject() { const [toggle, setToggle] = useState ...

What is the quickest and most effective method for toggling more than 10,000 checkboxes using Javascript/jQuery?

I am facing a challenge with a div that contains over 10000 checkboxes. Due to technical limitations, I have no choice but to work with this large number of checkboxes. This is not related to any academic task. <div class="well well-sm" style="min-hei ...

Is it possible to verify if a string in JavaScript contains both numbers and special characters?

I created this function to check if a string contains numbers and special characters, but it seems to not be working correctly let validateStr = (stringToValidate) => { var pattern = /^[a-zA-Z]*$/; if (stringToValidate&& stringToValidate.leng ...

Encountering a sudden problem while running gulp build due to a node_module - Surprising occurrence of Unexpected

Encountering an unexpected issue while running a gulp build process for a web app that I am struggling to resolve. The problem did not exist on the evening of 25/01/2019, but when attempting to execute the gulp build process this morning (30/01/2019), an ...

Activate event in jQuery when clicking on a blank area, away from any div element

I need help figuring out how to hide a div when empty space on the margins of the page is clicked, as opposed to just any area outside of the div. I've managed to achieve this functionality when certain other divs are clicked, but I'm stuck on im ...

Struggling to display data from .ts files in a mat-table or any HTML file within Angular 2

application-service.ts @Injectable() export class ScanServeripsService { constructor( private http: HttpClient, private httpUtils: HttpUtilsService, public env: EnvService ) { } API_FILE_URL = this.env.apiUrl + '/ap ...

Best location for Angular PWA update handler?

Running a PWA app led me to think about decluttering the application.component. To achieve this, I created a dedicated service to monitor PWA updates and alert the user: import { Injectable } from '@angular/core'; import { MatSnackBar } from &qu ...

I have created a Bootstrap 4 Form and my goal is to capture all the values inputted by users and have them sent to my email upon form submission

After creating a Form using Bootstrap 4, I am hoping to collect all the values submitted by the user via email. ...