Search code examples
angulartypescriptangular-reactive-formsangular-forms

Reactive Form with a Maximum of 3 selections


I am trying to add a question in my form with a maximum of three selections. The question is "What is your favorite fruit?". To clarify, users should be able to select up to 3 fruits and no more. I can do this easily if the code is not in a reactive form. However, since I can't use ngModel in a reactive form this has become a nightmare. Here is my code so far.

Ts file:

export class FruitSurveyComponent {
  fruitForm: FormGroup;
  availableFruits: string[] = ['Apple', 'Pear', 'Orange', 'Banana', 'Grapes', 'Pineapple'];

  constructor(private formBuilder: FormBuilder) {
    this.fruitForm = this.formBuilder.group({});
    this.availableFruits.forEach(fruit => {
      this.fruitForm.addControl(fruit, this.formBuilder.control(false));
    });
  }

  canSelectFruit(fruit: string): boolean {
    return this.fruitForm.value[fruit] || this.getSelectedFruitsCount() < 3;
  }

  getSelectedFruitsCount(): number {
    const selectedFruits = this.availableFruits.filter(fruit => this.fruitForm.value[fruit]);
    return selectedFruits.length;
  }

  onSubmit() {
    const selectedFruits = this.availableFruits.filter(fruit => this.fruitForm.value[fruit]);
    console.log('Selected Fruits:', selectedFruits);
  }
}

HTML:

<div class="container">
    <h1>Favorite Fruits Selector</h1>
    <form [formGroup]="fruitForm" (ngSubmit)="onSubmit()">
      <label>What are your favorite fruits? (Maximum of 3)</label>
      <div *ngFor="let fruit of availableFruits">
        <label>
          <input type="checkbox" [formControlName]="fruit" [disabled]="!canSelectFruit(fruit)" />
          {{ fruit }}
        </label>
      </div>
      <button type="submit">Submit</button>
    </form>
  </div>

Solution

  • If you can work with NgModel, you definitely able to work with Reactive Form.

    For the implementation, you should have these 3 functionalities:

    1. Checked/Unchecked the checkbox based on the form control value.

    2. Disable the checkbox when the selected fruits are more than 3 and the fruit was not selected. And re-enable it when the previous conditions are not fulfilled.

    3. A change event to add/remove the fruit from the form control value.

    <input type="checkbox" [checked]="isFruitSelected(fruit)" [disabled]="!canSelectFruit(fruit)" (change)="onFruitChecked(fruit)"  />
    

    You should change the FormGroup structure with a fruits control used to hold the fruits array instead of key-value pair.

    this.fruitForm = this.formBuilder.group({
      fruits: [[]],
    });
    
    canSelectFruit(fruit: string): boolean {
      return this.isFruitSelected(fruit) || this.getSelectedFruitsCount() < 3;
    }
    
    getSelectedFruitsCount(): number {
      return this.fruitForm.controls.fruits.value.length;
    }
    
    isFruitSelected(fruit: string) {
      return this.fruitForm.controls.fruits.value.indexOf(fruit) > -1;
    }
    
    onFruitChecked(fruit: string) {
      let selectedFruits: string[] = this.fruitForm.controls.fruits.value;
      let selectedFruitIndex = selectedFruits.indexOf(fruit);
    
      if (selectedFruitIndex > -1) selectedFruits.splice(selectedFruitIndex, 1);
      else selectedFruits.push(fruit);
    
      this.fruitForm.controls.fruits.setValue(selectedFruits);
    }
    
    onSubmit() {
      const selectedFruits = this.fruitForm.controls.fruits.value;
    
      console.log('Selected Fruits:', selectedFruits);
    }
    

    Demo @ StackBlitz