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:
- reactive forms with FormArray (removeAt and insert fuctions)
- other Ui components like CDK drag and drop
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...