Расчетные значения с использованием Angular FormArray

Я пытаюсь вычислить общее или другое значение на основе определенного значения в FormArray.

Я обнаружил, что при подписке на valueChanges и попытке передать весь массив чему-то вроде reduce «новое значение» отсутствует в родительской FormGroup.

Исходный пример на StackBlitz

Образец:

this.frm.get('arr')['controls'][i].valueChanges
  .subscribe((newVal) => {
    this.frm.get('total').patchValue(
      this.frm.get('arr').value.reduce((acc, curr) => {
        return acc + curr.v;
      }, 0)
    )
  });

Если arr имеет значения [0, 1, 2, 3, 4] и я изменяю первое значение на 1, я все равно получаю 10 вместо 11.

this.frm.get('arr').value показывает все начальные значения, а не обновленные значения.

Я новичок в реактивных формах, поэтому уверен, что мне просто не хватает чего-то фундаментального.

ОБНОВЛЕНИЕ (есть решение, но я все еще не понимаю):

Мне очень хотелось бы знать, почему я не могу подписаться на изменения всего массива, как предлагала Элисео.

Меня также интересует, почему существует такая разница между тем, где я использую .value - Если вся суть реактивных форм должна управляться моделью, а затем передавать frm.value в вашу функцию отправки, то почему .value так непоследовательно во всей иерархии форм?

ОБНОВЛЕНИЕ 2

Обновленное решение StackBlitz под руководством Элисео после его обновления

Я нашел решение, которое меня вполне устраивает. Он нигде не использует controls и кажется очень чистым. Предложенный Элисео подход подтолкнул меня попробовать это. Я считаю, что важно выполнить приведение к FormArray и выполнить на нем любые push. Это сохраняет привязку поля parent к остальной части вашего FormGroup или FormArray, поэтому оно остается включенным в событие valueChanges.

Теперь я считаю, что использование controls - это анти-шаблон для Angular Reactive Forms, но мне нужно найти больше указаний по этой теме.


person McTalian    schedule 27.02.2018    source источник


Ответы (2)


почему бы не прослушать изменения всего массива и не сделать итог с помощью newValues?

 //NOT ['controls'][i]
this.frm.get('arr')['controls'].valueChanges
  .subscribe((newVal) => {
    this.frm.get('total').patchValue(
      //use newVal
      newVal.reduce((acc, curr) => {
        return acc + curr.v;
      }, 0)
    )
  });

Это не работает, измените код buildForm для этого:

buildForm() {
    let controls:any[]=[];
    for (let i:number = 0; i < 5; ++i) {
      controls.push(this.builder.group({
        v: i
      }))
    this.frm = this.builder.group({
      total: [{value: '', disabled: true}],
      arr: this.builder.array(controls)
    });
      this.frm.get('arr').valueChanges
      .subscribe((newVal) => {
        let total:number=0;
        newVal.forEach(e=>{
          total=total+parseInt(e.v)});

        this.frm.get('total').patchValue(total,0)

      });
    }
  }

Основные изменения - это использование forEach, а не сокращение, использование parseInt и способ создания formArray.

person Eliseo    schedule 28.02.2018
comment
@McTalian, я не знаю, как сформирована ваша форма, возможно, это this.frm.get ('arr'). ValuesChanges. Другое дело, не забудьте написать valuesChange после создания формы - person Eliseo; 28.02.2018
comment
У меня есть StackBlitz выше, который показывает экосистему HTML, TS и Angular. Можете ли вы взглянуть и сказать мне, если я что-то делаю не так? - person McTalian; 28.02.2018
comment
Ваше обновление работает и кажется немного чище. Я снова заставил его работать с сокращением - я собираюсь добавить кнопку добавления новой строки и протестировать ее. Если это сработает, это будет очень хорошо для меня. Спасибо, что нашли время обновить! - person McTalian; 28.02.2018

Я заставил это работать.

this.frm.get('arr')['controls'][i].valueChanges
  .subscribe((newVal) => {
    this.frm.get('total').patchValue(
      this.frm.get('arr').value.reduce((acc, curr) => {
        return acc + curr.v;
      }, 0) + (+newVal.v)
    )
  });

Что-то в вашей форме немного странно. Все 5 элементов управления привязаны к одному и тому же formControlName "v". Я думаю, это может быть причиной того, что эта форма действует не так, как вам хотелось бы. Вышеупомянутое работает, но добавление newVal в конце, как это, кажется странным.

ОБНОВИТЬ:

Это работает для всех изменений полей.

this.frm.get('arr')['controls'][i].valueChanges
  .subscribe((newVal) => {
      const arr = <FormArray>this.frm.controls['arr'];
      let total = 0;
      arr.controls.forEach((c: any) => {
        console.log(c.value);
        total+= +c.value.v;
      });
      console.log('total',total);
      this.frm.get('total').patchValue(total)
  });

Не самый красивый код, который я когда-либо видел, но он работает. Избавьтесь от журналов консоли, и, возможно, вы сможете смириться с этим. :)

person R. Richards    schedule 28.02.2018
comment
Как мне получить FormArray, который не привязан к тому же formControlName? Не уверен, что слежу. - person McTalian; 28.02.2018
comment
Я не совсем уверен. Я попытался немного поиграть с этим в StackBlitz, но так и не смог найти на это хорошего ответа. Я могу еще поиграть с этим. Может быть, это не проблема, если это то, что вам нужно. - person R. Richards; 28.02.2018
comment
Завтра я попробую в реальном мире. Хотя я с вами, добавление newVal кажется странным, но я попробую! - person McTalian; 28.02.2018
comment
Попытка использовать newVal в конце в примере StackBlitz работает, только если вы измените одно поле. Если вы попытаетесь изменить любые другие поля, предыдущие новые значения не сохранятся :( - person McTalian; 28.02.2018
comment
Черт возьми! Думаю, это делает ответ совсем не лучшим. Я думал, что я что-то понял. Прости за это. - person R. Richards; 28.02.2018
comment
Это работает, я вернул reduce вместо forEach, и, похоже, это сработало. Мне бы хотелось понять, почему .value не работает на уровне массива, но работает на уровне управления. Кажется, есть несколько способов сделать что-то, но каждый работает немного по-своему, и я думаю, что я делаю что-то неправильно. - person McTalian; 28.02.2018