'How to remove the previously selected option from a drop-down menu in a table?
I am making a project on angular 7.It has a table with a column having dropdowns. The dropdown contains various languages. When a language is selected in a particular row, then it shouldn't appear in the dropdown in the subsequent row. How do I do that?
I tried deleting the selected language from the array using splice().But as it deletes the object, it is also not shown in the dropdown.
Following is the html -(it is the row of the table that defines the dropdown, and this row is dynamic)
 <tr *ngFor="let field of fieldArray;let i = index">
  <td><button class="btn" (click)="deleteFieldValue(i)"><i class="fa fa-trash"></i></button></td>
  <td class="select">
    <select #selectLang (change)="selected(selectLang.value,i)">
      <option value="undefined" disabled>Select Language</option>
      <option *ngFor="let lang of languageList" value={{lang.name}} [ngValue]="lang.name">{{lang.name}}</option>
    </select>
  </td>
  <td>
    <input id="fileUpload" name="fileUpload" type="file" name="upload_file" (change)=onFileChange($event)>
  </td>
</tr>
following is the typescript code -
languageList = [{'name': "Dothraki"},{'name': "Japanese"}, 
{'name':"German"},{'name':"French"},{'name': "Spanish"}, {'name': 
"Russian"}, {'name': "Italian"}];
  selectedLang;
  optionLang:string;
  fieldArray: Array<any> = [];
  newAttribute: any = {};
  fileUploadName: any;
  selected(lang:string,index:number){
    console.log(lang);
    // this.languageList.splice(index, 1);
    for(let i =0; i< this.languageList.length; i++) {
      if(this.languageList[i]['name'] === lang) {
        this.languageList.splice(i,1);
        break;
      }
    }
  }
  addFieldValue() {
    this.fieldArray.push("hg");
    this.newAttribute = {};
  }
  deleteFieldValue(index: number) {
    this.fieldArray.splice(index, 1);
}
  openFileBrowser(event:any){
    event.preventDefault();
    let element: HTMLElement = document.getElementById('fileUpload') as 
HTMLElement;
    element.click();
  }
  onFileChange(event:any){
    let files = event.target.files;
    this.fileUploadName = files[0].name;
    console.log(files);
  }
							
						Solution 1:[1]
you can solve the problem by holding a set of selected languages and display options conditionally based on whether an option/language is selected before or not.
create a Set to hold selected langs
selectedLangs = new Set<string>();
create a view query to get a list of all select elements
@ViewChildren("selectLang") langSelects: QueryList<ElementRef<HTMLSelectElement>>;
whenever a selection is made/changed on any of the select elements re-populate the selectedLangs set
  selected() {
    this.selectedLangs.clear();
    this.langSelects.forEach(ls => {
      const selectedVal = ls.nativeElement.value;
      if (selectedVal && selectedVal !== "undefined") this.selectedLangs.add(selectedVal);
    });
 }
whenever a field is deleted just remove that language from selectedLangs
  deleteFieldValue(index: number, lang: string) {
    this.selectedLangs.delete(lang);
    this.fieldArray.splice(index, 1);
  }
and when displaying options for a select check if it is currently selected on current select or already selected in another select *ngIf="selectLang.value === lang.name || !isSelected(lang.name)"
<ng-container *ngFor="let lang of languageList" >
  <option *ngIf="selectLang.value === lang.name || !isSelected(lang.name)" value={{lang.name}} [ngValue]="lang.name">
      {{lang.name}}
  </option>
</ng-container>
where isSelected is defined as 
  isSelected(lang: string) {
    return this.selectedLangs.has(lang);
  }
here is a working demo with full source https://stackblitz.com/edit/angular-dqvvf5
Solution 2:[2]
You can store the langs in an array make a function like
  lang = []; //define the array
  getLang(i, languageList) {
    return i == 0 ? languageList :
      this.getLang(i - 1, languageList.filter(x => x.name != this.lang[i-1]))
  }
So, you can has some like
<div *ngFor="let a of languageList;let i=index">
    <select  [(ngModel)]="lang[i]">
      <option value="undefined" disabled>Select Language</option>
      <option *ngFor="let lang of getLang(i,languageList)" 
          [value]="lang.name" >{{lang.name}}</option>
    </select>
</div>
But I don't like because each change force Angular to calculate all the options. So we are going to improve the code using FormArray and an array langList, and make sure that we can not choose the same language
First our variable and our function changed
  langList=[];
  getLangForFormArray(i, languageList) {
    return i == 0 ? languageList :
      this.getLang(i - 1, this.langList[i-1].filter(x => x.name != this.formArray.value[i-1]))
  }
We create a formArray
  formArray=new FormArray(this.languageList.map(()=>new FormControl(null)))
And in ngOnInit
  ngOnInit()
  {
    this.formArray.valueChanges.pipe(startWith(null)).subscribe(()=>{
      //create the langList array
      for (let i=0;i<this.languageList.length;i++)
         this.langList[i]=this.getLangForFormArray(i,this.languageList)
      //check no repeat values
      if (value)
      {
         value.forEach((x,index)=>{
         if (this.formArray.value.findIndex(v=>v==x)!=index)
             this.formArray.at(index).setValue(null,{emitEvent:false})
         })
      }
    })
  }
See that use formArray valueChanges with pipe(startWith(null)) to create at first the langList
The .html
<div *ngFor="let control of formArray.controls;let i=index">
    <select [formControl]="control">
      <option value="null" disabled>Select Language</option>
      <option *ngFor="let lang of langList[i]" 
          [value]="lang.name" >{{lang.name}}</option>
    </select>
</div>
And the demo in stackblitz
Solution 3:[3]
Define template that iterates trough form array controls
<mat-form-field *ngFor="let control of formArray.controls; let idx = index">
  <mat-select [formControl]="formArray.controls[idx]"
    (selectionChange)="onSelectionChange()">
    <mat-option [value]="blankLangId">''</mat-option>
    <mat-option *ngFor="let lang of langMap.get(idx)"
      [value]="lang.id">{{lang.name}}</mat-option>
  </mat-select>
</mat-form-field>
Define an array with all possible langs
public langs: LangModel[] = [// here your data]
public blankLangId = 0; // represents an empty selection
Define a Map that is going to hold an array of langs for specific dropdown
  public langMap= new Map<number, LangModel[]>();
Define a method that going to be execute every time selection is changed.
  public onSelectionChange(): void {
    this.normalizeLangsDropDowns();
  }
What normalizeLangsDropDowns method is going to do?
Inside this method we will loop trough form array controls
For each form control we will get all selected langs ids expect current form control
Then we will filter initial array of langs to remove selected ids and set the result into the Map.
Let's implement this logic.
Define a method that gets selected values from formControls expect provided index of control.
  private getSelectedLangIdsExcludingProvidedIndex(langIndex: number): Set<number> {
    return new Set(this.formArray.controls
      .map<number>(control => control.value)
      .filter((langId: number, index: number) => index!== langIndex && langId!== this.blankLangId)
    );
  }
Define a method that will return filtered array of langs
  private filterLangsForLangIndex(langIndex: number): LangModel[] {
    const langIdsToRemove = this.getSelectedLangIdsExcludingProvidedIndex(langIndex);
    return langIdsToRemove .size > 0
      ? this.langs.filter((lang=> !langIdsToRemove.has(lang.id)))
      : this.langs;
  }
Finally define the normalizeLangsDropDowns method
  private normalizeLangsDropDowns(): void {
    // loop trough each form control
    this.formArray.controls.forEach((langControl: AbstractControl, langIndex: number) => {
      // set filtered langs at current index
      this.langMap.set(langIndex, this.filterLangsForLangIndex(langIndex));
    });
  }
Here you go.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source | 
|---|---|
| Solution 1 | ysf | 
| Solution 2 | Eliseo | 
| Solution 3 | marc_s | 
