4
votes

I am using Angular's Reactive forms and one of the Form Controls is a select input. The options change dynamically and when the user has selected a value and then the options change, the old value is still the value of the Form Control, even when it's not longer in the options' list (and therefore not an acceptable value).

HTML:

<mat-select formControlName="example">
    <mat-option *ngFor="let example of examples_filtered" [value]="example">
        {{ example.frontend_name }}
    </mat-option>
</mat-select>

TypeScript:

this.myForm.controls['someControl'].valueChanges.subscribe(
    (value) => {
        this.examples_filtered = [];
        this.examples.forEach((example: Example, index: Number, array: Example[]) => {
          if (example.value_id === value.id) {
            this.examples_filtered.push(example);
          }
        });
    }
);

Since this Form Control uses Validators.required, the expected behavior is that the Form Control gets emptied (i.e. the value gets set to null) and its status is changed to 'INVALID'.

The actual result is that the old value from the previously filtered examples is still selected in the Form Control and the Validators.required marks the Form Control as valid.

Should I do this manually (i.e. custom code) or is there a mechanism that Angular supports that solves this?

1
Not sure if this might be issue, but seems like you've given wrong control name in code. Could you please try this.myForm.controls['example'].valueChanges instead of this.myForm.controls['someControl'].valueChanges ? - Just Shadow
@JustShadow This is because I am using the value of ['someControl'] to filter the examples. When the value of ['someControl'] changes I am filtering the examples, which changes the options' list. - Nikolay Nikolov
I don't think Angular can intercept your changes here, cause your not actually changing the formControl value (if your hiding a property doesn't mean you had change the selected value). Probably you should just write this.myForm.controls['example'].setValue(null) (nothing selected) or this.myForm.controls['example'].setValue(this.examples_filtered[0]) (first element selected) at the end of your subscribe function. - Federico Galfione
@FedericoGalfione I have added this.myForm.controls['example'].reset() and now I get the expected behavior. Thank you! - Nikolay Nikolov
@NikolayNikolov using reset() is not the way to go (and didn't work for me). Please see my answer below for a straightforward clean solution. - Bernoulli IT

1 Answers

2
votes

The following will work as desired. The crux is to use Angular's selectionChange event which will update your form control as directly as expected (before the attached event handler is entered).

Template

<mat-select formControlName="example" (selectionChange)="this.exampleSelectionChangedHandler($event)">
    <mat-option *ngFor="let example of examples_filtered" [value]="example">
        {{ example.frontend_name }}
    </mat-option>
</mat-select>

Component

public exampleSelectionChangedHandler(e: MouseEvent){
  this.examples_filtered = [];
  this.examples.forEach((example: Example, index: Number, array: Example[]) => {

  if (example.value_id === value.id) {
    this.examples_filtered.push(example);
  }
}

Oddly enough the selectionChange option is not "intellisensed" but is definitely a mat-select directive as we can read here:

@Output() selectionChange: EventEmitter

Event emitted when the selected value has been changed by the user.