Search code examples
angulartypescriptangular-reactive-formsformarray

Reactive forms - On valueChanges subscribe / update checkbox options


What I am trying to achieve is something like this, I have a result set with three options bmw, audi and opel, in the select I can only select one of the options, but if needed the user can still select not selected options(checkboxes) Result set:

cars = [
    { id: 1, name: 'bmw' },
    { id: 2, name: 'audi' },
    { id: 3, name: 'opel' }
];

Flow:

For example, lets say in the select I have selected bmw, so after the select I need to show two checkboxes, because audi & opel is still available options. If I change bmw to something else I also need to update the checkboxes to show those values what's not selected in the select.

form:

this.firstStepForm = this.fb.group({
  cars: [''], // select
  models: this.fb.array([]) // checkboxes
});

Setup checkboxes:

private setupControls(details: any): void {
let control = <FormArray>this.firstStepForm.controls.models;

details.forEach(x => {
  control.push(this.fb.group({
    id: x.id,
    name: x.name
  }))
})

Subscribe for changes:

this.firstStepForm.get('cars').valueChanges.subscribe((carId: number)

A simple example - stackblitz

At the moment, I don't really understand how to get the index of the specific car(selected in the select) and update/remove checkboxes.

Result:

  1. checkboxes only show the options that are not selected in the select
  2. if I change the selected value to something else, I need to update checkboxes to show options what's left from the result set.

Comments:

Maybe I don't even need to use removeAt(FormArray), but just a simple underscoreJs _filter & filter out based on the id? But how can I filter out FormArray based on specific value(:id)?

let magic = _.filter(arr, function (num) { return num != carId });

console.log('magic: ', magic);

Solution

  • try:

    export class AppComponent implements OnInit {
      cars = [
        { id: 1, name: 'bmw' },
        { id: 2, name: 'audi' },
        { id: 3, name: 'opel' }
      ];
      firstStepForm: FormGroup;
      constructor(private fb: FormBuilder) {}
    
      ngOnInit() {
        this.buildFirstStepForm();
        this.firstStepForm.get('cars').setValue(this.cars[0].id);
        this.onChange(this.cars[0].id); // initial first item select
      }
    
      onChange(e){
        let arr = this.cars.filter(item => item.id != e);
        this.models.controls = [];
        this.setupControls(arr)
      }
    
      get models(): FormArray {
        return <FormArray>this.firstStepForm.controls.models;
      }
    
      private buildFirstStepForm(): void {
        this.firstStepForm = this.fb.group({
          cars: [''],
          models: this.fb.array([])
        });
      }
    
      private setupControls(details: any): void {
        details.forEach(x => {
          this.models.push(this.fb.group({
            id: [x.id],
            name: [x.name]
          }))
        })
      }
    }
    

    html:

    <form [formGroup]="firstStepForm" (ngSubmit)="submit()">
        <p>Select:</p>
        <select formControlName="cars" (change)="onChange($event.target.value)">
            <option *ngFor="let car of cars; let i = index" [value]="car.id">
                {{car.name}}
            </option>
        </select>
      <br>
      <br>
      <br>
      <p>Checkboxes:</p>
      <div class="form-check" *ngFor="let type of models.controls; let i = index">
        <label formArrayName="models">
          <input type="checkbox">{{type.value.name}}
        </label>
      </div>
    </form>
    

    DEMO