Angular Form Validation: Ensuring Data Accuracy

Utilizing angular reactive form to create distance input fields with two boxes labeled as From and To.

HTML:

<form [formGroup]="form">
  <button (click)="addRow()">Add</button>
  <div formArrayName="distance">
    <div
      *ngFor="let item of form.get('distance').controls; let i = index"
      [formGroupName]="i"
      style="display: flex"
    >
      <input type="number" placeholder="From" formControlName="from" />
      <div><input type="number" placeholder="To" formControlName="to" /></div>
    </div>
  </div>
  <br /><br />
  <button type="submit" [disabled]="!form.valid">Submit</button>
</form>

TypeScript:

ngOnInit() {
  this.form = this.fb.group({
    distance: this.fb.array([]),
  });
  this.addRow()
}

addRow() {
  const control = this.form.controls.distance as FormArray;
  control.push(this.fb.group({
    from: ['',Validators.required],
    to: ['',Validators.required]
  }));
}

The default view displays two input boxes for from and to.

An 'add' button is present at the top. Clicking it adds rows with similar input fields arranged as an array.

The requirement is to prevent users from entering a previous row's to value or any lesser value in the current row's from input box.

Example scenario - In the first row, if user enters values like 0 and 5 for from and to respectively,

  "distance": [
    {
      "from": 0,
      "to": 5
    }
  ]

In the next row, when adding values to the from input box, any value equal to or less than 5 (which has already been entered) is considered invalid.

Therefore, the following configuration is incorrect:

{
  "distance": [
    {
      "from": 0,
      "to": 5
    },
    {
      "from": 5,
      "to": 10
    }
  ]
}

Values such as "from": 5,, "from": 4(or)3(or)2(or)1, are not valid in the second row.

Only values equal to or greater than 6 are acceptable.

This validation needs to be applied for each row by comparing with the previous row's to value.

Assistance is requested on implementing this type of validation to restrict users from entering a previous row's to value or a lesser value in the current row's from field.

Working Example: https://stackblitz.com/edit/disable-group-control-value-on-another-control-value-for-j58atx

Edit:

Attempted using input change event listener like below,

<input type="number" (input)="onInputChange($event.target.value)" placeholder="From" formControlName="from">

Refer to the link https://stackblitz.com/edit/disable-group-control-value-on-another-control-value-for-ymfpkj for details, seeking confirmation on correctness of approach.

Please advise on any necessary changes to the procedure if deemed incorrect.

Answer №1

After considering my options, I made the decision to split the two conditions. You can view the updated code on this new stackblitz

  ngOnInit() {
    this.form = this.fb.group({
      distance: this.fb.array([], this.distanceValidator()),
    });
    this.addRow()
  }

  addRow() {
    const control = this.form.controls.distance as FormArray;
    control.push(this.fb.group({
      from: ['', Validators.required],
      to: ['', Validators.required]
    }, { validator: this.greaterValidator() }));
  }
  setDefault() {
    const control = this.form.controls.distance as FormArray;
    this.default.forEach(data => {
      control.push(this.fb.group({
        from: [data.from, Validators.required],
        to: [data.to, Validators.required]
      }, { validator: this.greaterValidator() }));
    });
  }
  greaterValidator() {
    return (fa: FormGroup) => {
      return fa.value.to && fa.value.to < fa.value.from ? { error: "from greater than to" } : null;
    }
  }
  distanceValidator() {
    return (fa: FormArray) => {
      let ok = true;
      for (let i = 1; i < fa.value.length; i++) {
        ok = (!fa.value[i].from || fa.value[i].from > fa.value[i - 1].to) && (!fa.value[i].to || fa.value[i].to > fa.value[i - 1].from);
        if (!ok)
          return { error: "from/to yet included", index: i }
      }
      return null
    }
  }

And here is the corresponding .html code:

<form [formGroup]="form">
    <button (click)="addRow()">Add</button>
  <div formArrayName="distance" >
    <div 
      *ngFor="let item of form.get('distance').controls; let i = index" 
      [formGroupName]="i" 
      style="display: flex">
      <input type="number" 
        placeholder="From" 
        formControlName="from">
      <div>
        <input type="number"
          placeholder="To" 
          formControlName="to">
      </div>
      <span *ngIf="item.errors">*</span>
      <span *ngIf="form.get('distance')?.errors && form.get('distance')?.errors.index==i">**</span>
    </div>
  </div>
  <div *ngIf="form.get('distance')?.errors">{{form.get('distance')?.errors.error}}</div>
  <br><br>
  <button type="submit" [disabled]="!form.valid"> Submit </button>
</form>
<button (click)="setDefault()"> Set Default Values </button>

Update: I have decided that errors will only be displayed when they are found and not beyond. Additionally, if the fields before 'from' and 'to' are empty, no error should be given. To achieve this, we can convert them to numbers by adding a '+' sign like so:

let ok = (!fa.value[i].from || fa.value[i].from > +fa.value[i - 1].to)
        && (!fa.value[i].to || fa.value[i].to > +fa.value[i - 1].from);

(notice the "+" in +fa.value[i-1].to and +fa.value[i-1].from)

In order to display errors more effectively, imagine that in a scenario with 6 rows, lines at position 0, 3, and 4 (0 being the first row) report an error like this:

{error:"there are errors",indexError:",0,3,4,"}

This allows us to include conditional statements within the *ngFor loop, such as:

  <span *ngIf="form.get('distance')?.errors && 
      form.get('distance')?.errors.indexError.indexOf(','+i+',')>=0">
       **
  </span>

Therefore, our revised distanceValidator function looks like this:

  distanceValidator() {
    return (fa: FormArray) => {
      let indexError:string="";
      for (let i = 1; i < fa.value.length; i++) {
        let ok = (!fa.value[i].from || fa.value[i].from > +fa.value[i - 1].to) && (!fa.value[i].to || fa.value[i].to > +fa.value[i - 1].from);
        if (!ok)
          indexError+=','+i;
      }
      return indexError?{error:"there are errors",indexError:indexError+','}:null
    }

Some may argue that returning an array of errors would be better, but it complicates the process of identifying specific rows with errors using interpolation. Hence, the approach mentioned above. Also, while it's possible to check all preceding rows for errors, I believe keeping the code concise is crucial. Remember, the distanceValidator function is executed multiple times.

To explore further examples, you can visit another updated stackblitz link

Answer №2

To implement custom validation, I opted to use the validation within the same component.

ngOnInit() {
    this.form = this.fb.group({
      distance: this.fb.array([], this.distanceValidator()),
    });
    this.addRow()
  }
  distanceValidator() {
    return (fa: FormArray) => {
      let isValid = true;
      let isValid2 = fa.value.length ? (!fa.value[0].to || !fa.value[0].from) || fa.value[0].to > fa.value[0].from : true;
      if (!isValid2)
        return { error: "From value is greater than To value" }
      for (let i = 1; i < fa.value.length; i++) {
        if (fa.value[i].from && fa.value[i].to )
        {
        isValid = (fa.value[i].from > fa.value[i - 1].to || fa.value[i].to < fa.value[i - 1].from);
        isValid2 = (fa.value[i].to > fa.value[i].from);
        if (!isValid)
          return { error: "From/To values are overlapping" }
        if (!isValid2)
          return { error: "From value is greater than To value" }
        }
      }
      return isValid && isValid2 ? null : !isValid?{ error: "From value is overlapping" }:{ error: "From value is greater than To value" }
    }
  }

You can display the error message as follows:

 <div *ngIf="form.get('distance')?.errors">
     {{form.get('distance')?.errors.error}}
 </div>

For more details, check out the [forked stackblitz example][1]

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

Guarantee that unique events are triggered following h5validate events

I am currently experiencing a problem with H5Validate where its events are overriding the custom events I have implemented, causing my events not to trigger because they are based on H5validate events. To work around this issue, I have resorted to setting ...

Encountering a ETIMEDOUT error "ip address" when making a Node.js request

What I'm doing in the code I am currently reading a text file containing approximately 3500 links. Subsequently, I iterate through each link to filter out the relevant ones and make requests to retrieve their status codes, links, and page titles usin ...

Using [(ngModel)] on a field within an object that is nested inside another object

I am facing a challenge as I attempt something that may or may not be feasible. Within an Angular form, I have an object structured like this: export class NewUserRegisterModelDTO{ userData:UserDetailModelDTO; roles:RoleModelDTO[]; ownerData:O ...

Placing the video at the center of the background image

                    Can someone assist me with a design issue I'm facing? I have two divs within a section. The left div contains a heading and a button, while the right div contains a video. However, when I try to add a background image to ...

How can HostBinding be used to target a custom directive in order to deliver either a success or error message and show it on

I am incorporating a custom directive to display specific server messages/errors following an http request. For example, in the response or error section, I want to target the custom directive and present the emphasized message. The directive is already e ...

Discover additional information within extensive text by leveraging Angular 4 features

Looking for a solution to truncate text and limit it to 40 words, then display the full text when clicking on a "see more" link. I experimented with ng2-truncate, read-more-directive, and ng-text-truncate-2, but they were not compatible with Angular 4. ...

How can I show the text name as a selection in the React Material UI Autocomplete component while sending the ID as the value?

Material UI Autocomplete component functions properly, but I am interested in extracting object.id as the value for the onSelect event (rather than object.name). Essentially, I want to display the object.name as the select item label, while retrieving obje ...

Dealing with undefined Ajax values in PHP

Every time I call the function, I receive an undefined value and it's baffling me as to what could be causing this issue. You are logged in as undefined, Error undefined Ajax script: function Submit() { console.log('asd') $.ajax({ ...

The Angular service/value is failing to retrieve the updated variable from the $(document).ready() function

Currently, I'm facing an issue with my Angular service/value. It seems to be grabbing the original variable instead of the new one that is supposed to be inside $(document).ready(). Here's the relevant code snippet: var app = angular.module("app ...

Converting JSON data into an HTML table

I'm struggling to convert a JSON object into an HTML table, but I can't seem to nail the format. DESIRED TABLE FORMAT: Last Year This Year Future Years 45423 36721 873409 CURRENT TABLE FORMAT: Last Year 45423 This ...

What is the method to access and examine the attributes of a range in Office.js?

I am encountering an issue while attempting to retrieve the values from cell B2 and create a conditional statement based on those values. Despite my efforts, I continue to receive an error message without any clear understanding of its cause. Please refe ...

Combining subscriptions in Angular

For the ngOnInit of a specific component, I have a need to subscribe to two different actions: To make a get request using an already existing Angular service that will return a list of items (sourceOptions in the code). To retrieve the route.queryParams ...

How to ensure jQuery runs only once, even when the back button is pressed in the

How can I ensure that jQuery only executes once, even when navigating back to the page using the browser's back button? When my page initially loads, the jQuery runs fine. However, if I navigate away from the page and then return using the back button ...

unable to reinstall due to removal of global typing

After globally installing Moment typing with the command typings install dt~moment --save --global Checking the installed typings using typings list shows: ├── <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="93fffcf7f2e0 ...

Can a Unicode character be overwritten using a specific method?

Is there a way to display a number on top of the unicode character '♤' without using images? I have over 200 ♤ symbols each with a unique number, and using images would take up too much space. The characters will need to be different sizes, a ...

Error: Unable to access undefined properties (reading 'url')

I am currently working on creating a drag-and-drop card game and I have encountered an issue with the react-dnd library. When using data from the file, everything works fine, but if I have to fetch the data externally, it throws an error. This problem see ...

Just a straightforward Minimum Working Example, encountering a TypeScript error TS2322 that states the object is not compatible with the type 'IntrinsicAttributes & Props & { children?: ReactNode; }'

Currently, I am immersed in a project involving React and Typescript. I am grappling with error code TS2322 and attempting to resolve it. Error: Type '{ submissionsArray: SubmissionProps[]; }' is not assignable to type 'IntrinsicAttributes ...

Troubleshooting: Issues with JQuery validation for cross-domain URLs

I'm fairly new to using JQuery and I have a specific requirement which involves validating a link from another domain. If the validation is successful, I need to redirect (open the page in a new window) to that link. Otherwise, I should display an ale ...

Tips for displaying external JavaScript code in an alert box:

Is there a way to display external JavaScript code in an alert or another location by accessing the variable value s_code? (function(){ var newscript = document.createElement('script'); newscript.type = 'text/javascript'; ...

Trigger a PrimeNG p-datatable event when a new row is inserted

My p-datatable is linked to a list of entries. I am trying to focus on newly created rows dynamically, specifically on an input within the new row. How can this be achieved? Every time a new entry is added to the list, a new row is appended in the datatab ...