1

I'm trying to create a form that contains a list of select controls (just for the sake of simplicity).

I'm not going to add new selects to the form, I just want to keep track of the selected option in each of the select controls. enter image description here enter image description here This is the json I use to generate the select controls:

    sports = [
    {
      sportId: '1',
      desc: 'Football',
      categories: [
        {
          categId: '1',
          desc: 'U-18'
        },
        {
          categId: '1',
          desc: 'U-20'
        },
        {
          categId: '2',
          desc: 'U-23'
        }
      ]
    },
    {
      sportId: '2',
      desc: 'Volleyball',
      categories: [
        {
          categId: '3',
          desc: 'Junior'
        },
        {
          categId: '4',
          desc: 'Youth'
        }
      ]
    },
    {
      sportId: '3',
      desc: 'Tennis',
      categories: [
        {
          categId: '5',
          desc: 'Singles'
        },
        {
          categId: '6',
          desc: 'Doubles'
        }
      ]
    }
  ];

And this is the html of my form

<form [formGroup]="registrationForm"(submit)="save()">
          <div class="form-group">
            <label for="firstname">First Name</label>
            <input
              type="text"
              class="form-control"
              id="firstname"
              formControlName="firstName"
              placeholder="First Name"
            />
          </div>
          <div class="form-group">
            <label for="lastname">Last Name</label>
            <input
              type="text"
              class="form-control"
              formControlName="lastName"
              id="lastname"
              placeholder="Last Name"
            />
          </div>
          <div class="form-group" *ngFor="let sport of sports; let i = index">
            <label attr.for="sport_{{i}}">{{sport.desc}}</label>
            <select class="form-control" id="sport_{{i}}">
                <option value="0">Select one</option>
                <option *ngFor="let cat of sport.categories" [value]="cat.categId">{{cat.desc}}</option>               
              </select>
          </div>
          <button type="submit" class="btn btn-primary">Submit</button>
        </form>

Building the form for the firstName and lastName is straightforward:

  constructor(private fb: FormBuilder) {}
  ngOnInit(): void {
    this.registrationForm = this.fb.group({
      firstName: '',
      lastName: ''
    });
  }

But I'm not sure about how to keep track of the selected options on each of the different select controls.

I'd really appreciate it if you could help me here.

EDIT

Here's a demo

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

3 Answers 3

2

Each control that you want to keep track of could have an entry in the Form Group:

  constructor(private fb: FormBuilder) {}
  ngOnInit(): void {
    this.registrationForm = this.fb.group({
      firstName: '',
      lastName: '',
      // <- Each additional control should be listed here
      football: '', 
      volleyball: '',
      tennis: ''
    });
  }

Think of the Form Group as the set of data you want to keep track of. So if you said you were not interested in dynamically adding sports, just keeping track of their values, then each of their values becomes an entry in the Form Group.

You can find a "brute force" style solution here: https://stackblitz.com/edit/angular-i7ntj3

If you want to dynamically add the controls (even though you don't plan to add more), you can use a FormArray as shown in your example. It looks like you were primarily missing the [formControlName] property and the code populating the FormArray.

Component

  registrationForm: FormGroup;

  get categoryOptions(): FormArray {
    return <FormArray>this.registrationForm.get('options');
  }

  constructor(private fb: FormBuilder) { }

  ngOnInit(): void {
    this.registrationForm = this.fb.group({
      firstName: '',
      lastName: '',
      options: this.fb.array([])
    });

    // Add the sports to the FormArray
    this.sports.forEach(s =>
      this.categoryOptions.push(new FormControl()));
  }

  save() {
    console.log(JSON.stringify(this.registrationForm.value));
  }

NOTE: The FormGroup needs to contain all of the form controls you want to track. In the above example, we track two text fields and a FormArray. A FormArray contains either a set of Form Controls or a set of Form Groups. And since those Form Array elements are not being added dynamically, we need to create them manually. That's the purpose of the forEach in the code above.

Template

      <div formArrayName="options">
        <div *ngFor="let sport of sports; let i=index">
          <label attr.for="i">{{sport.desc}}</label>
          <select class="form-control" id="i" [formControlName]="i">
            <option value="0">Select one</option>
            <option *ngFor="let cat of sport.categories" [value]="cat.categId">{{cat.desc}}
            </option>
          </select>
        </div>
      </div>

NOTE: The formArrayName must be set to the name of the FormArray as defined in the FormGroup. The ngFor loops through each sport and sets up an index that will map to each element of the array. The formControlName for each select element is then set to the array index.

You can find this FormArray solution here: https://stackblitz.com/edit/angular-reactive-select-deborahk

Hope this helps.

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

5 Comments

Thank you Deborah. Just one last thing. Do I necessarily have to use the index as the formControlName? I ask this because what I wanted to store inside the each item of the FormArray were objects of this shape {<teamId>: <selectedOptionId>}
Currently what's being in the FormArray is a array of numbers. Is there any way I can store objects of the shape I mentioned on my previous comment? Once again, thank you Deborah,You're being extremely kind
For what purpose? What is in the form array is only tracking how the form works. You can store the data to the database completely different from what the Form Array is storing. How would you use this extra information in the Form Array?
Because that way it would be easier to tell which team the numbers inside the array refer to. Besides, what if need to keep track a set of inputs, not just a single select input?
Thank you Deborah, I just had to make a few changes to make it work the way I wanted it to. Here's the result: stackblitz.com/edit/angular-reactive-select-deborahk-xkks2m . Now each item of the array is a FormGroup of the shape : { "teamId": "1", "categId": "3" }. This way not only I got rid of this sort of 'magic' numbers, but also created the array with the same structure that is dictated by the backend. Once again, thank you Deborah. You've been really helpful.
2

change you ngOnInit() to this.

 ngOnInit(): void {

    this.registrationForm = this.fb.group({ 
      firstName: '0',
      lastName: '0',
      sportsFormArray: this.fb.array([])
    });

    this.sports.forEach((s) => {
      this.sportsFormArray.push(this.fb.control(0));
    });
  }  

and in html, the array of control code will be like this

<div formArrayName="sportsFormArray">
  <div *ngFor="let sport of sports; let i=index">
    <label>{{sport.desc}}</label>
    <select class="form-control" id="sport_{{i}}" [formControlName]="i">
      <option value="0">Select one</option>
      <option *ngFor="let cat of sport.categories" [value]="cat.categId">{{cat.desc}}</option>              
    </select>
  </div>
</div>

1 Comment

Do I necessarily have to use the index as the formControlName? I ask this because what I wanted to store inside the each item of the FormArray were objects of this shape {<teamId>: <selectedOptionId>} . Currently what's being in the FormArray is a array of numbers. Is there any way I can store objects of the shape I mentioned?
1

You might using wrong structure to generate dynamic forms controls. You need to mention that what dynamic control you are using like below:

profileForm = this.fb.group({
    firstName: ['', Validators.required],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
    aliases: this.fb.array([
      this.fb.control('') // this states that, there will be dynamic form control array
    ])
  });

Then after to read the values of control you can use like below;

this.profileForm.get('aliases') as FormArray

You can edit demo from office site here.

If you can provide stackblitz to check issue, it will helpful for those who want to answer and directly provide code change in to that.

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.