How to get index of changed item in angular form array

38,097

Solution 1

I would not use the valueChanges here, it would be fired excessively, specially if the array has many values.

You could have a change event on each value, and just pass the value and index, something like

(keyup)="changeValue(line.controls.ProductName.value, i)"

But this kind of fights the purpose of the reactive form.

Even though you have plenty of values that you do not want to show in form, and it is values that the user cannot modify, I'd just add them to the form anyway as form controls, nothing says that you need to show them in template!

This way, if you build the form in a way that it would match your model order, you could just directly assign the form value to your model upon submit. I highly recommend this approach.

Solution 2

export class CustomFormArray {
     public form: FormGroup;

     public get someArray(): FormArray {
          return this.form.get('someArray') as FormArray;
     }

     constructor(private _fb: FormBuilder) {
          this.form = _fb.group({
               someArray: _fb.array([])
          });

          this.someArray.controls.forEach(
               control => {
                   control.valueChanges.subscribe(
                        () => {
                       console.log(this.someArray.controls.indexOf(control)) // logs index of changed item in form array
                        }
                   )
               }
          )
     }
}    

Solution 3

I found a method to help who wants to find the element that changes the value

Here is my html

<mat-form-field>
   <mat-label>Material</mat-label>
   <mat-select formControlName="material" (ngModelChange)="materialChange($event)">
      <mat-option *ngFor="let objetct of materiais" [value]="objetct">   
        {{objetct?.nome}}
      </mat-option>
  </mat-select>
</mat-form-field>

This is my Angular ts file

@Component({
  selector: 'app-solicitar-material-form',
  templateUrl: './solicitar-material-form.component.html',
  styleUrls: ['./solicitar-material-form.component.css']
})
export class SolicitarMaterialFormComponent implements OnInit {

  public form: FormGroup;
  private object: SolicitacaoMaterialDTO;
  public materiais: MaterialDTO[] = [];

  @Output() mudouValor: EventEmitter<Unidade> = new EventEmitter();

  constructor() {
    this.form = new FormGroup({
      "itens": this.formBuilder.array([])
    });
  }

  ngOnInit() {
    this.getMateriais();
 }

  getMateriais() {
    this.materialService.findAll().subscribe((res) => {
      if (res) {
        this.materiais = res;
      }
    })
  }

  // this is the method that make me find the item that changed
  materialChange($event) {
    (<FormArray>this.form.get('itens')).controls.map((material) => {
      if (material.value.material == $event) {
        material.patchValue({
          unidadeMedida:$event.unidadeMedida
        });
      }
    });
  }

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

  clickAddItem() {
    this.itens.push(this.createItem());
  }

  createItem(): FormGroup {
    return this.formBuilder.group({
      material: this.formBuilder.control(null, [Validators.required]),
      unidadeMedida: this.formBuilder.control(null),
      quantidadeSolicitada: this.formBuilder.control(null, [Validators.required]),
    });
  }
}
Share:
38,097

Related videos on Youtube

Kevin
Author by

Kevin

Work primarily with asp.net mvc applications with jquery or knockout. More recently started using more Angular and dabbling in PHP, ruby on rails, and iOS's swift in my spare time. My life outside of software development is largely spent with running and training for USA championships. So far my highest finish is 13th in the USA championships, maybe I can one day be top 3 and go to the olympics :)

Updated on July 09, 2022

Comments

  • Kevin
    Kevin almost 2 years

    I'm using Angular 4 with reactive forms. I have a form array that I am trying to tie to an array I keep track of in my component. I'm using reactive forms so I can have the validation, so I don't want to use the template forms approach.

    I add items to the form array like so:

    createFormWithModel() {
      this.orderForm = this.fb.group({
        orderNumber: [this.order.ProductBookingOrder],
        orderDate: [this.order.PurchaseOrderIssuedDate],
        lineDetailsArray: this.fb.array([])
      })
    
      const arrayControl = <FormArray>this.orderForm.controls['lineDetailsArray'];
      this.order.ProductBookingDetails.forEach(item => {
        let newGroup = this.fb.group({
          ProductName: [item.ProductName],
          Quantity: [item.ProductQuantity.Quantity],
          UOM: [item.ProductQuantity.UOM],
          RequestedShipDate: [item.RequestedShipDate]
        })
      })
    }
    

    The orderForm is obviously my reactive forms FormGroup. the order is my object I get from my API and I want to update its values, including the line details. I think I should use 'valueChanges.subscribe' on each newGroup but I'm not sure how to get the index of the item that was changed. Any thoughts?

         newGroup.valueChanges.subscribe('i want value and index some how' => {
    this.order.ProductbookingDetails[index].ProductName = value.ProductName;
        });
    

    Here's the HTML for this portion:

    <tbody formArrayName="lineDetailsArray">
            <tr [formGroupName]="i" *ngFor="let line of orderForm.controls['lineDetailsArray'].controls; index as i">
              <td><input class="form-control" type="text" placeholder="Product Name" formControlName="ProductName" required/></td>
              <td><input class="form-control" type="number" step=".01" (focus)="$event.target.select()" placeholder="Quantity" formControlName="Quantity"/></td>
              <td><input class="form-control" readonly formControlName="UOM"></td>
              <td><date-picker formControlName="RequestedShipDate" format="L" [showClearButton]="false"></date-picker></td>
              <td><button type="button" (click)="deleteLineDetail(i)">Remove</button></td>
            </tr>
          </tbody>
    
    • CharanRoot
      CharanRoot almost 7 years
      why do you want save each line details? why can't you send total model
    • Kevin
      Kevin almost 7 years
      There is a ton of extra stuff I get my order model from the API, but I only want some of that stuff in the orderForm model. The orderForm model is just so that I can update the information that's allowed to be updated by the user. So I can't just send off the orderForm model to the API when things are done because it'll be missing all of the other information. I want my order model's values to be updated when the form inputs change
  • Kevin
    Kevin almost 7 years
    This is what I ended up doing, kind of. I cycled through each formArray control and rebuilt my line-detail and reassigned that new list to my order list of details. I'll try just straight up turning my form into my complex typescript object now. Thanks!
  • AT82
    AT82 almost 7 years
    No problem! Yeah, I think it's a good idea to just modify the form as such and get rid of having to capture the change events. In my opinion cleaner and more effective. Good luck, happy coding and have a great weekend! :)
  • Sᴀᴍ Onᴇᴌᴀ
    Sᴀᴍ Onᴇᴌᴀ over 6 years
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. Please read why it isn't wise to post code-only answers