htmlangularngforangular-ngmodel

Select not showing correct value after change


The following code generates six <select> tags

<span *ngFor="let key of orderedKeys; let i=index;">
                Position {{i + 1}}:
                <select (change)="changePosition($event.target.value, i+1)" >
                    <option *ngFor="let option of options" [value]="option" [selected]="option === object[key].name">{{option}}</option>
                </select>
                <hr>
</span>

The function changePosition swaps the position of two keys in orderedKeys so that the selected option should change for two select tags but one select which should change the value is only focused but is not changing the selected value but why? I also tried it with [(ngModel)]=object[key].name but the same behavior is observed.


Solution

  • It's all about angular change detection... (when nested structural directives combined with conditions and user changes). In such cases we can force update conditions on next browser MacroTask:

    <span *ngFor="let key of orderedKeys; let i=index;">
       Position {{i + 1}}:
       <select (change)="changePosition($event.target.value, i+1)" >
         <option *ngFor="let option of options" [value]="option" [selected]="flag && (option === object[key].name)">{{option}}</option>
         </select>
         <hr>
    </span>
    

    ts:

    orderedKeys = ['keyA', 'keyB', 'keyC', 'keyD'];
      object = {
        'keyA': {name: 'nameA'},
        'keyB': {name: 'nameB'},
        'keyC': {name: 'nameC'},
        'keyD': {name: 'nameD'},
      }
      options = ['nameA', 'nameB', 'nameC', 'nameD'];
      flag = true;
    
      changePosition(val: string, pos: number) {
        let selectedKey = Object.keys(this.object).find(key => this.object[key].name === val);
        let keyToSwap = this.orderedKeys[pos - 1];
        
        let newOrderedKeys = [...this.orderedKeys];
        newOrderedKeys[pos - 1] = selectedKey;
        newOrderedKeys[this.orderedKeys.indexOf(selectedKey)] = keyToSwap;
        this.orderedKeys = newOrderedKeys;
        
        //force update on next browser MacroTask
        this.flag = false;
        setTimeout(() => {
          this.flag = true;
        }, 0);
      }
    

    finally in these priority or ranking cases other implementations may be better:

    Update1- Reactive Forms solution

    ReactiveFormsModule must be imported in your module.

    template:

    <form [formGroup]="formGroup">
      <div formArrayName="selects">
        <div *ngFor="let ctrl of controlsArray; let i=index;" [formGroupName]="i">
          Position {{i + 1}}:
          <select (change)="refreshOrder(i+1)" formControlName="name">
            <option *ngFor="let option of options" [value]="option">{{option}}</option>
          </select>
          <button *ngIf="i>0" (click)="move(i, -1)">move up</button>
          <button *ngIf="i<controlsArray.length-1" (click)="move(i, 1)">move down</button>
          <hr>
        </div>
      </div>
    </form>
    

    ts:

      //parent form group
      formGroup: FormGroup;
    
      //getter functions
      get controlsArray() {
        return this.selectsFormArray.controls;
      }
      get selectsFormArray() {
        return this.formGroup.get('selects') as FormArray
      }
    
      constructor() {
        let formArray = new FormArray([]);
        //create form array based on ordered keys array
        this.orderedKeys.forEach(key => {
          formArray.insert(formArray.length, new FormGroup({
            name: new FormControl(this.object[key].name)
          }))
        });
    
        this.formGroup = new FormGroup({selects: formArray});
      }
    
      refreshOrder(pos: number) {
        let val = this.controlsArray[pos-1].get('name')?.value;
        
        if (!val) return;
        let selectedKey = Object.keys(this.object).find((key: string) => this.object[key].name === val);
        let keyToSwap = this.orderedKeys[pos - 1];
        
        if(!selectedKey || !keyToSwap)
        return;
    
        //updating ordered keys array
        let selectedKeyPrevIndex = this.orderedKeys.indexOf(selectedKey);
        let newOrderedKeys = [...this.orderedKeys];
        newOrderedKeys[pos - 1] = selectedKey;
        newOrderedKeys[selectedKeyPrevIndex] = keyToSwap;
        this.orderedKeys = newOrderedKeys;
        
        //refresh form control
        this.controlsArray[selectedKeyPrevIndex].get('name')?.setValue(this.object[keyToSwap].name);
      }
    
      move(index: number, direction: number) {
        let temp = this.selectsFormArray.at(index);
        this.selectsFormArray.removeAt(index);
        this.selectsFormArray.insert(index+direction,temp);
    
        //updating ordered keys array too:
        let clonedOrderedKeys = [...this.orderedKeys];
        clonedOrderedKeys[index] = this.orderedKeys[index+direction];
        clonedOrderedKeys[index+direction] = this.orderedKeys[index];
        this.orderedKeys = clonedOrderedKeys;
      }
    

    FormArray is one of the three fundamental building blocks used to define forms in Angular, along with FormControl and FormGroup and it can be an array of FormControl, FormGroup or FormArray instances.This way you could have very complicated and nested forms...