FormArray Show Default Value of Input field on checkbox change event

Viewed 836

I have a FormArray with two controls selectItem which is a Checkbox and SelectAmount which is an Input text field. Initially, I am displaying default values from an Array of Objects in the Input field which is editable when checkbox is checked. Now, If I uncheck the checkbox then I want the Input field to display default value which is the reversalAmount property as shown in the code. Can anyone Please help me in this.

            <ul formArrayName="items">
                <li class="reverse-list-item" *ngFor="let option of reversePaymentOptions ; let i=index" [formGroupName]="i">
                    <div class="select-all">
                        <mat-checkbox formControlName="selectItem" [(ngModel)]="reversePaymentOptions[i].isChecked" (change)="getSelectedItem(option)"></mat-checkbox>
                    </div>

                    <div class="reverse-amount-change" *ngIf="togglePartialReversalWithdrawal && option.PartialWithdrawEnabled">
                        <div class="form-control-wrapper">
                            <mat-form-field class="form-full-width">
                                <mat-label>GBP</mat-label>
                                <input type="text" matInput formControlName="selectAmount" (focus)="onfocusAmount(reversePaymentOptions[i])" [(ngModel)]="reversePaymentOptions[i].reversalAmount">
                            </mat-form-field>
                        </div>                            
                    </div>
                </li>
            </ul>
this.reversePaymentOptions = [
      {
        thumb: './assets/images/withdrawal/visa.svg.png',
        name: 'Visa',
        cardType: 'cc',
        reversalAmount: 20.00,
        isChecked: false,
        PartialWithdrawEnabled: true
      },
      {
        thumb: './assets/images/withdrawal/trustly.svg.png',
        name: 'Trustly',
        cardType: 'noncc',
        reversalAmount: 20.00,
        isChecked: false,
        PartialWithdrawEnabled: false
      },
      {
        thumb: './assets/images/withdrawal/paypal.svg',
        name: 'Paypal',
        cardType: 'wallet',
        reversalAmount: 10.00,
        isChecked: false,
        PartialWithdrawEnabled: true
      },
      {
        thumb: './assets/images/withdrawal/skrill.svg.png',
        name: 'Skrill',
        cardType: 'noncc',
        reversalAmount: 40.00,
        isChecked: false,
        PartialWithdrawEnabled: true
      },
      {
        thumb: './assets/images/withdrawal/maestro.svg.png',
        name: 'Maestro',
        cardType: 'noncc',
        reversalAmount: 10.00,
        isChecked: false,
        PartialWithdrawEnabled: true
      },
      {
        thumb: './assets/images/withdrawal/mastercard.svg.png',
        name: 'Master Card',
        cardType: 'noncc',
        reversalAmount: 30.00,
        isChecked: false,
        PartialWithdrawEnabled: true
      }
    ];

    ngOnInit() {
       this.form = this.formBuilder.group({      
         items: this.formBuilder.array([])
      });
      this.data();
      this.getTotalAmount();
    }

    get formArr() {
      return this.form.get('items') as FormArray;
    }

    this.reversePaymentOptions.map(d =>
      this.formArr.push(this.formBuilder.group({
        selectItem: new FormControl(d.isChecked),
        selectAmount: new FormControl(d.reversalAmount, [Validators.required, Validators.min(1), 
        Validators.max(d.reversalAmount)])
      }))
    );

2 Answers

Since you're using ngModel the reversalAmount values under the reversePaymentOptions will get changed, so there is no way for you to retrieve the original value. So let's solve that first by removing all ngModel (you don't need it). Use the Reactive Form implementation (that you already have) instead.

Also modify the getSelectedItem() in the HTML to pass the in the control index and the checked status as well:

<ul formArrayName="items">
  <li class="reverse-list-item" *ngFor="let option of reversePaymentOptions ; let i=index" [formGroupName]="i">
      <div class="select-all">
          <mat-checkbox formControlName="selectItem" (change)="getSelectedItem(option, i, $event.checked)"></mat-checkbox> <-- Notice the extra parameters in getSelectedItem
      </div>

      <div class="reverse-amount-change" *ngIf="togglePartialReversalWithdrawal && option.PartialWithdrawEnabled">
          <div class="form-control-wrapper">
              <mat-form-field class="form-full-width">
                  <mat-label>GBP</mat-label>
                  <input type="text" matInput formControlName="selectAmount" (focus)="onfocusAmount(reversePaymentOptions[i])" >
              </mat-form-field>
          </div>                            
      </div>
  </li>
</ul>

If for some reason, you require reversePaymentOptions to have their reversalAmount modified, create a new non-referenced copy of the array (using things like lodash cloneDeep)

In the TS, modify getSelectedItem:

getSelectedItem(option, index, checked) {
  ...
  if (!checked) {
   this.formArr.controls[index].get('selectAmount').setValue(this.reversePaymentOptions[index].reversalAmount);
  }
  
}

I suggested another aproach, a [ngModel] standalone that get the value of reverse or the value of the formControl. The only you need take account is, when send the value to the api is check the values of the array and if is unchecked give the defualt value

puff, in code we need one function to get the formArray

  get itemsArray()
  {
    return this.form.get('items') as FormArray
  }

Our .html must be like (see that we iterate over itemsArray.controls). See that, as our variable with use to iterate over the array is group, we can access to the value of the FormControl as group.get('selectItem') and group.get('selectAmount') (*)

<form [formGroup]="form">
    <ul formArrayName="items">
        <li class="reverse-list-item" *ngFor="let group of itemsArray.controls ; let i=index" [formGroupName]="i">
            <div class="select-all">
                <mat-checkbox formControlName="selectItem">
                  {{reversePaymentOptions[i].name}}
                </mat-checkbox>
            </div>
            <div class="reverse-amount-change">
                <div class="form-control-wrapper">
                    <mat-form-field class="form-full-width">
                        <mat-label>GBP</mat-label>
                        <input type="text" matInput
                           [ngModel]="group.get('selectItem').value?
                              group.get('selectAmount').value:
                              reversePaymentOptions[i].reversalAmount"
                           (ngModelChange)="group.get('selectItem').value &&
                              group.get('selectAmount').setValue($event)"
                           [ngModelOptions]="{standalone:true}"
                        >
                    </mat-form-field>
                </div>
            </div>
        </li>
    </ul>
</form>

Ah!, dont' forget in submit make some like

  submit(form:FormGroup)
  {
    if (form.valid)
    {
      form.value.items.forEach((x:any,index:number)=>{
        if (!x.selectItem)
          x.selectAmount=this.reversePaymentOptions[index].reversalAmount  
      })
    }
  }

(*) In some version of angular, according the restrict of typescript give error use directlty the variable over you iterate the controls. If is your case create an auxiliar function

  getGroup(index:number)
  {
    return (this.itemsArray.at(index) as FormGroup)
  }

And replace the group.get(...) by getGroup(i).get(...)

a very very ugly stackblitz

Update sometimes I forget that I'm use material-angular and you can get/set the value of the inputs using [value] and (change). So you can use

<input type="text" matInput [disabled]="!group.get('selectItem').value"
                   [value]="group.get('selectItem').value?
                       group.get('selectAmount').value:
                       reversePaymentOptions[i].reversalAmount"
                   (change)="group.get('selectItem').value && 
                       group.get('selectAmount').setValue($event.target.value)"
                    
            >
Related