3

I want to have a select with options that are read from other inputs and when the input changes, my selection will change too.

All select and inputs are reactive forms. The problem is when I change the value of the inputs, the select options only update when I set the options value to the form control. If I set the options value to the value of form control, they don't get updated.

Example code is here: https://stackblitz.com/edit/angular-5zfocw

Template:

<form [formGroup]="form">
  <div formArrayName="opts" *ngFor="let opt of form.get('opts').controls; let i = index">
  <div [formGroupName]="i">
    {{i}}:  
    <input type="text" formControlName="name">
  </div>
  </div>
</form>

<form [formGroup]="form">
  <label> Selection:</label>
  <select formControlName="selection">
    <option *ngFor="let opt of form.get('opts').controls; let i=index" [value]="opt.value.name">
      {{opt.value.name}}
    </option>
  </select>
</form>
<hr>

<pre>
 selection: {{ form.get('selection').value | json}}
</pre>

Compoent:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, FormArray } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  public form: FormGroup
  constructor(private fb: FormBuilder){
    this.form = this.fb.group({
      opts: this.fb.array([
        this.fb.group({name: ['N0']}),
        this.fb.group({name: ['N1']}),
        this.fb.group({name: ['N2']})
      ]),
      selection: [{value: {}}]
    })
  }
}

If I change line 13 in the HTML template to:

<option *ngFor="let opt of form.get('opts').controls; let i=index" [value]="opt">

Then the option and the input is sync perfectly. However, I am not able to get the value of the selection.

If the line is change back to [value]="opt.value.name", then select N2, then change N2 input, my selection will be lost and the value does not update.

what I want

I want 1. Select N2 from drop down. 2. Change N2 to something else such as N22 in input. 3. The selection should update to N22. And the value N22 should be displayed at the bottom of the page.

What should I do?

6
  • Unable to understand! Checked Stackblitz but nothing is there. There are two forms, what do you wnt? Commented Sep 11, 2019 at 5:15
  • Why there are two Forms? Commented Sep 11, 2019 at 5:21
  • I want 1. Select N2 from drop down. 2. Change N2 to N22 in input. 3. The selection should update to N22. And the value of N22 should displayed at the bottom Commented Sep 11, 2019 at 5:30
  • Check this:stackblitz.com/edit/angular-ao48y4 Commented Sep 11, 2019 at 5:33
  • I don’t see anything but a hello in your page @prashant pimpale Commented Sep 11, 2019 at 5:36

2 Answers 2

4

you need check when the array change. For this, subscribe to this.form.get('opts').valueChanges. Use pairwise to get the old value and change the value of selection.

In code, after create the form

this.form.get('opts').valueChanges.pipe(
  startWith(this.form.value.opts),
  pairwise())
  .subscribe(([old,value])=>{
     const index=old?old.findIndex(x=>x.name==this.form.get('selection').value):-1
     if (index>=0)
        this.form.get('selection').setValue(value[index].name,{emit:false})
  })

The stackblitz

Update if we can remove/add some value, the array change too. So we need take account if we remove an element. so, the code becomes:

this.form.get('opts').valueChanges.pipe(
  startWith(this.form.value.opts),
  pairwise())
  .subscribe(([old, value]) => {
    if (old.length > value.length) { //if we remove some value
                //we search if "selection" is in the new value
      const index = value? value.findIndex(x => x.name == this.form.get('selection').value) : -1;
      if (index == -1) //if not, simple equal to ""
        this.form.get('selection').setValue("", { emit: false })
    }
    else { //Only change the value if we are not removing
      const index = old ? old.findIndex(x => x.name == this.form.get('selection').value) : -1
      if (index >= 0)
        this.form.get('selection').setValue(value[index].name, { emit: false })
    }
  })

see a new stackblitz

Sign up to request clarification or add additional context in comments.

6 Comments

What does startWith do?
pairwise return two values (old,value) after receive two changes -the next change return again two values-, startWith send one value before begin listen the changes, so the "real" first change give us old and value. It's usuall use startWith in an observable when we want change a variable. Imagine you want disable a input depending the value of a check-box. You can disable/enable the input and listen the changes of the check to enable/disable it, but why not listen the changes and beging with the initial value of the check? learnrxjs.io/operators/combination/startwith.html
One more question, what if any one of the input fields can be deleted by the users? The comparing old and new value method cannot really distinguish which one is deleted if two names are the same. Is this a limitation of reactive form? Is there a better way?
When we delete a value, we don't need worry about the old value. Only check if the value "selection" is in the new value to equal to "" if is not in the value. Take account that if some values are repeated, the selection get's the first value . e.g. if selection get "N2" and change "N2" by "N1", if you show the combo, you'll see that is in the index 1
Make a lot of sense
|
2

After a lot of try and error, I figured out another solution (or workaround?) Just post it here for reference.

Instead of using the string value as the value of the options, we can directly use the FormControl as the value, and it connected to the input, thus both of them are sync together.

https://stackblitz.com/edit/angular-musy61

This section in the HTML file, see [ngValue]="opt"

  <select formControlName="selection">
    <option *ngFor="let opt of opts.controls; let i=index" [ngValue]="opt">
      {{opt.value.name}}
    </option>
  </select>

Try select N2 from the select, then edit N2 in the input. See how everything update together.

The issue is when retrieving the value out, it is a form control. So the value is in a cumbersome selection.value.value.name

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.