Verify that each field in the form contains a distinct value

I have a formarray with nested formgroups. How do I ensure that the elements within each formgroup are unique?

Here is an example of my form setup:

form: FormGroup = this.formBuilder.group({
  fields: this.formBuilder.array([]),
});

private createField() {
  return this.formBuilder.group({
    label: ['', [Validators.required, uniqueLabelsValidator()]],
  });
}

public addField() {
  (this.form.get('fields') as FormArray).push(this.createField());
}

Is there a way to validate that all "label" values inside "fields" array are distinct? If not, how can I set a validation error on any duplicate values found?

Answer №1

Your FormArray consists of FormGroups, allowing you to inspect a single FormControl within the FormGroup.

(If your FormArray were made up of FormControls, you would need to implement validators on the "FormArray" itself.)

By applying a validator to a control, we gain access to its parent and grandparent elements. In this scenario, the component's parent is the formGroup and the parent of the parent is the formArray.

The challenge arises when adding a validator to a FormControl that relies on others; in such cases, we must ensure that the control is also validated whenever related controls are modified.

I will attempt to clarify through comments, so please make sure to comprehend the logic instead of merely copying the code.

  uniqueLabelsValidator() {
    return (control: AbstractControl) => {
      //only validate if the control has a value
      if (control.value) {
        //retrieve the formGroup
        const group = control?.parent as AbstractControl;

        //obtain the FormArray
        const array = control?.parent?.parent as FormArray;

        if (array) {
          //determine the index of the formGroup
          //e.g., when modifying the second "label," 
          //the group corresponds to the second formGroup (index=1)

          const index = array.controls.findIndex(
            (x: AbstractControl) => x == group
          );

          //update and validate all subsequent "labels" after the specified index
          //in the given example, it refers to labels in the third or fourth position

          setTimeout(()=>{
            array.controls.forEach((group:AbstractControl,i:number)=>{
              i>index && group.get('label')?.updateValueAndValidity()
            })
  
          })

          //create an array containing the values of the labels

          const values = array.value.map((group: any) => group.label);

          //check for any duplicate values before the changed "label"
          if (
            values.find((x: any, i: number) => x == control.value && i < index)
          )
            return { duplicate: true };
        }
      }
      return null;
    };
  }

You can explore this functionality further in the StackBlitz demo.

UPDATE: The previous validator only marks the "duplicate" as an error, not both. To flag both as duplicates, replace i<index and i>index with i!=index.

uniqueLabelsValidator() {
    return (control: AbstractControl) => {
      ...
          setTimeout(()=>{
            array.controls.forEach((group:AbstractControl,i:number)=>{
              i!=index && group.get('label')?.updateValueAndValidity()
            })
  
          })
          ...
          if (
            values.find((x: any, i: number) => x == control.value && i != index)
          )
            return { duplicate: true };
        }
      }
      ...
    };
  }

Answer №2

If you're looking for a robust solution, I recommend exploring the @rxweb/reactive-form-validators library.

To understand how to implement unique validation, you can refer to this article: Unique Validation (Please note that the article also includes code samples for implementing unique values without RxWeb validators)

  1. Start by importing RxReactiveFormsModule into your app module or standalone component.
import {
  RxReactiveFormsModule,
  RxwebValidators,
} from '@rxweb/reactive-form-validators';
  1. Integrate RxwebValidators.unique() validation along with an optional message parameter in the form control.
private createField() {
  return this.formBuilder.group({
    label: ['', [Validators.required, RxwebValidators.unique({ message: "Label must be unique." })]],
  });
}
  1. Display the unique validation message on the HTML page.
<small
  class="form-text text-danger"
  *ngIf="getField(i).controls['label'].errors"
>
  {{getField(i).controls["label"].errors?.["unique"]?.message}}
</small>

If you want to see a live demonstration, check out this example on StackBlitz: Demo @ StackBlitz

Answer №3

this validation function will return null, but it will flag control errors for duplicated values

import { ValidatorFn, AbstractControl, ValidationErrors, FormArray } from '@angular/forms';

export function checkDuplicates(fieldName: string): ValidatorFn {
  return (formArray: AbstractControl): ValidationErrors | null => {
    const values = new Set<string>();

    if (formArray instanceof FormArray) {
      formArray.controls.forEach((control) => {
        const fieldControl = control.get(fieldName);
        const value = fieldControl.value as string;

        if (!fieldControl.hasError('required')) {
          if (values.has(value)) {
            fieldControl.setErrors({ duplicateValue: true });
          } else {
            values.add(value);
            if (fieldControl.hasError('duplicateValue')) {
              fieldControl.setErrors(null);
            }
          }
        }
      });
    }

    return null;
  };
}

to implement this custom validator, apply it like this:

form: FormGroup = this.formBuilder.group({
  fields: this.formBuilder.array([], [checkDuplicates('value')]),
});

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

Error TS2339: The 'selectpicker' property is not found on the 'JQuery<HTMLElement>' type

Recently, I integrated the amazing bootstrap-select Successfully imported bootstrap-select into my project with the following: <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstra ...

What should be transmitted to the front-end following the successful validation of a token on the server?

My process starts with a login page and then moves to the profile page. When it comes to handling the token on the backend, I use the following code: app.use(verifyToken); function verifyToken(req, res, next) { if (req.path === '/auth/google&ap ...

Exchange information between two components and a service in a continuous loop

My Angular application retrieves a JSON object from an API, and I want to accomplish this through a service. The app has a search component that queries the service, which in turn fetches the data. View example diagram Next, the second component needs t ...

Error in Typescript/React: Unable to access the property 'MaxEmailLength' as it is undefined

I am facing an unusual problem with TypeScript. I have two static classes that are mutually referencing each other and causing issues. Class ValidationHelper (single file) import { ValidationErrors } from '../dictionary/ValidationErrors'; ex ...

Steps to successfully deploy an Angular application using Jenkins

I'm in the process of deploying my Angular application to an AWS EC2 instance. For version control, I'm utilizing Bitbucket and Jenkins for continuous integration. ...

I encountered a problem while integrating antd and moment.js in my React project

I am currently using the antd date-picker in my React project with TypeScript. Encountered an error: Uncaught Type Error: moment is not a function. If anyone has a solution, please assist me. .tsx file:: const dateFormat = 'MM-DD-YYYY'; < ...

Loading screen displayed while transitioning between routes within Angular

I have been struggling to implement a loading spinner in my project. How can I display a loading screen when changing routes in Angular? Here is the HTML code snippet: <div *ngIf="showLoadingIndicator" class="spinner"></div> ...

Enhancing the internal styling of ngx-charts

While working on the ngx-charts Advanced Pie Chart example, I noticed that the numbers in my legend were getting cut off. Upon inspecting the CSS, I discovered that a margin-top of -6px was causing this issue: https://i.stack.imgur.com/oSho1.png After so ...

What is the process for invoking a microservice (constructed in express js) from an angular application that is currently communicating with a sails js backend?

I had initially developed an application with Angular frontend and Sails JS backend. However, I recently separated some of the backend functions into a micro-service using Express. Now, I am looking for guidance on how to call these functions from my mai ...

`How can I extract HTMLElements from slots in vue3?`

When attempting to develop a Layer component, I encountered some challenges. Here is the code: // Wrapper.vue <template> <slot v-bind="attrs"></slot> </template> <script lang="ts" setup> import { defi ...

Tips for sequentially arranging and rearranging an array of numbers, even when duplicates are present

Encountered a perplexing issue that has me scratching my head in an attempt to visualize a solution. Currently, I am working with an array of objects that appears as follows: let approvers = [{order:1, dueDate: someDate},{order:2, dueDate: someDate}, ...

Opting out of notifications using Angular's NGXS

I'm new to NGXS in Angular and have recently learned that you don't need to manually unsubscribe when using the async pipe. However, I am currently subscribing to both query parameters and dispatched actions. Do I still need to manually unsubscri ...

MEAN Stack Development: Why does the timestamp stored by Node.js appear different from the time on my laptop in Dev Mode?

Can someone assist me with properly displaying the correct datetime stamp? For example, the time on my laptop is: 02-Oct-2021 11:14am (this is in India) The time stored by Node and displayed in Angular is 2021-10-02T05:44:09.022Z, which is 06:30 behind m ...

Looking for recommendations on the best tools to use for starting an Angular 2 project?

After successfully creating an Angular 2 web app on my computer, I encountered a problem when trying to build it for production. Instead of using my project files, angular-cli generated a "Hello World" app. The confusion arose because I was initially using ...

What is the best way to extract the inner text from an element within a QueryList<T>?

function addGoatToVaccinationGroup(goatObject: {earTag:string, id:number}){ for (let i = 0; i < this.vaccineQueue.length; i++){ if (this.vaccineQueue[i].earTag === goatObject.earTag){ this.vaccineQueue.splice(i, 1); ...

Restrictive discriminated union via function argument

I am in possession of a shop that organizes a variety of types based on their IDs interface Dog { type: "dog"; woofs: string; } interface Cat { type: "cat"; meows: string; } type Pet = Dog | Cat; type AnimalState = Record<string, Pet ...

What is the best way to consistently position a particular row at the bottom of the table in AG-grid?

I have successfully implemented a table using AG-grid. Here is the code snippet: .HTML <ag-grid-angular #agGrid style="width: 100%; height: 100%; font-size: 12px;" class="ag-theme-alpine" [rowData]=&quo ...

Unable to choose Typescript as a programming language on the VSCode platform

Recently, I encountered an issue while using Visual Studio Code with TypeScript. Even though TypeScript is installed globally, it is not showing up in the list of file languages for syntax highlighting. Despite trying various troubleshooting methods such a ...

The imported package is not functioning properly within the project

I've recently developed a Typescript Package and I want to test it in an application before publishing it on NPM. The main file (index.ts) of the package is structured like this => import Builder from './core/builder'; export default ...

Angular configuration files fail to be exchanged promptly

I am utilizing Angular 9 with the View Engine compiler. I have two separate files where I hold environment variables: environment.ts: export const environment: any = { production: false, }; environment.prod.ts: export const environment: any = { ...