'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 |