Передать индекс в валидатор FormArray Control - PullRequest
2 голосов
/ 01 марта 2020

У меня есть FormArray с простыми входами. Я хочу передать функции валидатора (custom) индекс входа в FormArray, так как мой валидатор основан на этом. Например, я хочу, чтобы каждый последующий AbstractControl имел больше букв, чем предыдущий (это всего лишь пример, передача индекса - это то, чего мне действительно нужно достичь).

Я имею в виду, смогу ли я получить что-то вроде этого

ngOnInit(): void {
    this.items = this.items?.length ? this.items : this.getDefaults(this.size)

    const inputs: FormArray = this.formGroup.get('inputs') as FormArray
        for (let item of this.items) {
            inputs.push(this.fb.control(
            item.value,
            this.validator(),
            this.asyncValidator(),
            ))
        }
}

private validator(): ValidatorFn {
    return (abstractControl: AbstractControl/*, indexInArray */) => {
        // const lenOfPrev = this.items[indexInArray - 1].value.length
        if (abstractControl.value.length <= 0 /* lenOfPrev */) {
            return {
                notLargeEnough: 'should be bigger'
            }
        }
        return null
    }
}

Ответы [ 2 ]

1 голос
/ 01 марта 2020

Ваша цель состоит в том, чтобы проверить элемент управления против другого элемента управления. Тот факт, что они находятся в массиве форм, немного не имеет значения.

При написании пользовательского валидатора вы связаны его интерфейсом :

interface ValidatorFn {
  (control: AbstractControl): ValidationErrors | null
}

Так что если вы Вы не можете вставить ссылку на другой элемент управления в интерфейсную функцию. Единственная другая опция - ввести другой элемент управления в саму функцию.

  ngOnInit(): void {
    this.items = this.items?.length ? this.items : this.getDefaults(this.size)

    const inputs: FormArray = this.formGroup.get('inputs') as FormArray
    this.items.forEach((item, i) => {
      const previousControl = i > 0 ? inputs.controls[i - 1] : null;      
      inputs.push(this.fb.control(
        item.value,
        this.validator(),
        this.asyncValidator(),
      ));
    });    
  }

  private validator(previousControl: AbstractControl): ValidatorFn {
    return (control: AbstractControl) => {
      if (!previousControl || control.value.length > previousControl.value.length) {
        return null;
      }

      return {
        notLargeEnough: 'should be bigger'
      };
    }
  }

Вы должны учитывать тот факт, что первый элемент управления не ' у него есть предыдущий элемент управления для сравнения.

DEMO: https://stackblitz.com/edit/angular-9atrvn

Возможно, вы захотите добавить что-то, что обновит валидацию в связанном элементе управления, когда значение изменяется, поскольку по умолчанию этого не происходит.

РЕДАКТИРОВАТЬ

Если ваш массив изменяется в течение срока его службы, вы можете использовать FormControl.setValidators(validators) для обновления валидаторов для затронутых элементов.

Если вы начинаете с массива

A <- B <- C <- D <- E

И удаляете одно:

A <- B <- D <- E

Тогда единственная зависимость, которая имеет изменено D -> C, теперь становится D -> B. Поэтому вам необходимо обновить валидатор для D, чтобы он зависел от B.

Примечание: использование setValidators перезапишет все существующие валидаторы. Поэтому, если вы используете дополнительные валидаторы, они также должны быть включены.

Развернутая демонстрация: https://stackblitz.com/edit/angular-f93sgn

0 голосов
/ 01 марта 2020

просто добавьте аргументы в функцию Validator

private validator(indexInArray:number): ValidatorFn { //<--here
    return (abstractControl: AbstractControl) => {
      //you can use indexInArray here
     if (indexInArray==0)  //For the first give always valid
         return null; 
      //I change this.items by this.form.value
      const lenOfPrev = this.form.value[indexInArray - 1].value.length
      if (abstractControl.value.length <= lenOfPrev.length) {
        return {
          notLargeEnough: 'should be bigger'
        }
      }
      return null
    }
  }

Но, если вы используете значения компонента, вам нужно "связать" валидатор

this.items.forEach((item, i) => {
      const previousControl = i > 0 ? inputs.controls[i - 1] : null;      
      inputs.push(this.fb.control(
        item.value,
        this.validator(i).bind(this), //<--see the argument and the "bind"
        this.asyncValidator(),
      ));
    }); 

Но, извините , Этот подход имеет соответствующий недостаток: что произойдет, если вы измените, например, 2-й вход с «ааа» на «ааааа» ?. Если ваш 3-й вход «bbbb», то до того, как он был действителен, но не после изменения. Но Angualr может принять это, потому что 3-й вход не меняется. Итак, почему бы не использовать Validator для всех formArray. Как пользовательский валидатор возвращает объект, вы можете вернуть, например, массив [false, false, true, false] и спросить об этом значении

  private validatorArray()
  {
    return (formArray:FormArray)=>{
      if (!formArray.value)
        return null;
      const result= (formArray.value.map((x:any,i:number)=>{
        if (i==0)
           return false;
        return (x.length<=formArray.value[i-1].length)
      }))
      return result.find(x=>x)?{error:result}:null;
    }
  }

В form.errors?.error[i] у вас есть, если элемент управления недействителен

Вы можете увидеть два подхода в этот стек стека

ПРИМЕЧАНИЕ: я использую непосредственно formArray FormControls, но это тот же

Upate после прочтения ответа Курта, на самом деле это не обязательно bind (this) в валидаторе, просто передайте предыдущий элемент управления, как сказал Курт.

...