Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

177
Vistas
Angular - Uniqueness validator in FormArray

I created a custom validator to validate uniqueness in my FormArray. I want to show error when specific(s) value(s) is/are already in array.

The problem is that it isn't working as expected.

Actual behavior:

Steps to reproduce:

  • Add 3 "inputs" - address;
  • Fill input 1;
  • Fill input 2 with different value;
  • Fill input 3 with the same value of input 1; (no errors appear, neither in input 1 nor in input 3)

Expected behavior:

If the same values appears in "X groups", their specific inputs must show the error.

In the case described above the errors should appear on input 1 and 3.

Supposing that I have 4 inputs:

  1. value: stack
  2. value: overflow
  3. value: stack
  4. value: overflow

The 4 inputs must show an error, because all of them are duplicates.


static uniqueBy = (field: string, caseSensitive = true): ValidatorFn => {
  return (formArray: FormArray): { [key: string]: boolean } => {
    const controls = formArray.controls.filter(formGroup => {
      return isPresent(formGroup.get(field).value);
    });
    const uniqueObj = { uniqueBy: true };
    let found = false;

    if (controls.length > 1) {
      for (let i = 0; i < controls.length; i++) {
        const formGroup = controls[i];
        const mainControl = formGroup.get(field);
        const val = mainControl.value;    
        const mainValue = caseSensitive ? val.toLowerCase() :  val;

        controls.forEach((group, index) => {
          if (i === index) {
            // Same group
            return;
          }

          const currControl = group.get(field);
          const tempValue = currControl.value;
          const currValue = caseSensitive ? tempValue.toLowerCase() : tempValue;
          let newErrors;

          if ( mainValue === currValue) {
            if (isBlank(currControl.errors)) {
              newErrors = uniqueObj;
            } else {
              newErrors = Object.assign(currControl.errors, uniqueObj);
            }

            found = true;
          } else {
            newErrors = currControl.errors;

            if (isPresent(newErrors)) {
              // delete uniqueBy error
              delete newErrors['uniqueBy'];

              if (isBlank(newErrors)) {
                // {} to undefined/null
                newErrors = null;
              }
            }
          }

          // Add specific errors based on condition
          currControl.setErrors(newErrors);
        });
      }

      if (found) {
        // Set errors to whole formArray
        return uniqueObj;
      }
    }

    // Clean errors
    return null;
  };
}

You can check it here DEMO.

about 4 years ago · Santiago Trujillo
2 Respuestas
Responde la pregunta

0

In your code is used nested for loop where you are interleaving errors.

Here is how validation state looks for each iteration:

  0      [null, "{"uniqueBy":true}", null]

  1      ["{"uniqueBy":true}", "{"uniqueBy":true}", null]

  2      [null, "{}", null]

http://plnkr.co/edit/MTjzQ9KiJHJ56DVAZ155?p=preview (Add three addresses and observe output)

In code below i am clearing errors only once before for loop statement and don't delete errors anymore.

controls.map(formGroup => formGroup.get(field)).forEach(x => x.errors && delete x.errors['uniqueBy']);
for (let i: number = 0; i < controls.length; i++) {
    const formGroup: FormGroup = controls[i] as FormGroup;
    const mainControl: AbstractControl = formGroup.get(field);
    const val: string = mainControl.value;

    const mainValue: string = caseSensitive ? val.toLowerCase() :  val;
    controls.forEach((group: FormGroup, index: number) => {
        if (i === index) {
            return;
        }

        const currControl: any = group.get(field);
        const tempValue: string = currControl.value;
        const currValue: string = caseSensitive ? tempValue.toLowerCase() : tempValue;
        let newErrors: any;

        if ( mainValue === currValue) {
            if (isBlank(currControl.errors)) {
                newErrors = uniqueObj;
            } else {
                newErrors = Object.assign(currControl.errors, uniqueObj);
            }
            currControl.setErrors(newErrors);
            find = true;
        }
    });
}

Plunker Example

about 4 years ago · Santiago Trujillo Denunciar

0

I couldn't add a comment so I'm just putting this as a new answer. I modified yurzui's plunker a little as the form was staying invalid after correcting a field with a duplicate value.

How to reproduce error in yurzui's plunker:

  1. Fill in name correctly
  2. Fill in street with value 'x'
  3. Add a new address with a street value of 'x'
  4. Change the second street to something other than 'x'

The form is still invalid because the first 'street' FormControl has errors set to '{}' instead of null. I just noticed that dev_054 did it in his original post but anyway, here is a plunker with the changes: http://plnkr.co/edit/nQeQ01fjnTlcp3FgEnWL?p=preview

code was:

controls.map(formGroup => formGroup.get(field)).forEach(x => x.errors && delete x.errors['uniqueBy']);

code is now:

controls.map(formGroup => formGroup.get(field)).forEach(x => {
    if (x.errors) {
        delete x.errors['unique-by'];
        if (isBlank(x.errors)) {
            x.setErrors(null);
        }
    }
});

In the plunker I also fixed the way the parameter caseSensitive was interpreted. It'll work as excepted now.

about 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda