nebular
nebular copied to clipboard
NbSelect doesn't display the [selected] option if data is dynamic
Issue type
I'm submitting a ... (check one with "x")
- [x] bug report
- [ ] feature request
Issue description
Current behavior: I have a NbSelect populated with options from the database. Neither using [selected] or ([ngModel]) makes it display the selected value. As soon as I pass static data it works as intended.
Expected behavior: It should automatically select the selected option.
Steps to reproduce: Put NbSelect on page, have a simple MySQL database to support the exchange of data, take the data and put them as options.
Related code: Unfortunately since it requires my localhost mySQL here, I can't provide a stackblitz, but I can include some snippets.
header.component.html
<nb-select (selectedChange)="changeLang($event)" ([ngModel])="selectedLang">
<nb-option *ngFor="let lang of languages" [value]="lang">{{ lang.short }}</nb-option>
</nb-select>
header.component.ts
service.exp.subscribe((exp: any) => {
this.languages = exp.langs.map(x => {
return {
short: x.short,
name: x.name
}
});
this.selectedLang.next(this.languages.filter(x => x.short === translate.currentLang)[0]);
});
header.service.ts
this.server.getLangsList().subscribe((langs: any) => {
this.exp.next({
langs: langs
});
});
server.service.ts
getLangsList() {
return this.request('GET', `http://localhost:8080/langs`);
//return of([{"idLang":1,"short":"en","name":"English","dateFormat":"YYYY-MM-DD","dateTimeFormat":"YYYY-MM-DD HH:mm:ss"}]);
}
Now, in the last snippet, the two rows return the same Observable. In the caso of the second (static data) it displays the [selected] value correctly, in the first it doesn't.
I'm not sure it's related to the issue 2088, so I started a new issue #2088
Other information:
npm, node, OS, Browser
Node version 10.16.0
npm version 6.10.3
Windows 10, on Chrome
Angular, Nebular
ngx-admin v4.0.1, nebular v4.1.2
Same here. If I use the standard HTML select, it updates without problems.
until it´s fixed: setting "selected" in ngAfterViewInit
works for me....
I was facing exactly the same issue also yesterday. If you are using FormControls you can set the value of the nb-select in your component and it will work.
step1: this.fb.group({ shop: [this.getDefaultElement(this.shoe.shop), [Validators.required]],....
any news on this?
I was facing exactly the same issue also yesterday. If you are using FormControls you can set the value of the nb-select in your component and it will work.
step1: this.fb.group({ shop: [this.getDefaultElement(this.shoe.shop), [Validators.required]],....
Can you please your code snippet?? because I have tried anything and it still doesn't work
I was facing exactly the same issue also yesterday. If you are using FormControls you can set the value of the nb-select in your component and it will work.
step1: this.fb.group({ shop: [this.getDefaultElement(this.shoe.shop), [Validators.required]],....
Are you using nb-select & nb-option with dynamic data ? I have tried but it still doesn't work !
Is there any workaround on this?
@alex88 doesn´t this work for you?
@michabbb that works when initially setting the select right after component loads, however after a while I also have to change its value from outside (from another server request) and at that time the component doesn't show the updated value (even if the correct item is selected when opening the dropdown)
The only workaround that worked for me is using the protected member selectOption which takes an instance of NbOptionComponent object, look at the following example:
this.ddlCoutry.selectOption(this.ddlCoutry.options.find( (item, _index, _options) => { return item.value.id === this.shop.country.id; }, ));
this will work for both single and multiple select as follows:
this.shop.classifications.forEach((classification) => { this.ddlClassification.selectOption(this.ddlClassification.options.find( (item, _index, _options) => { return item.value.id === classification.id; }, ));});
The weirdest thing is that the only working function is protected!
and to overcome this issue you can use dynamic property access with bracket notation, like so
this.ddlCoutry['selectOption'](this.ddlCoutry.options.find( (item, _index, _options) => { return item.value.id === this.shop.country.id; }, ));
I have used @shireefadel's workaround as follows:
- in the html, add a
#select
on the nb-select element.
<nb-select fullWidth multiple formControlName="mySelected" #select>
- Add an angular viewchild to the nb-select element
@ViewChild('select') selectElem: NbSelectComponent;
- Add a workaround timeout and call it when the dynamic content changes
changeMyDynamicContent() {
...Logic here...
setTimeout(() => {
if (this.selectElem) {
const selectedOptions: NbOptionComponent[] = [];
for (const option of this.selectElem.options['_results']) {
if (mySelected.includes(option.value)) {
selectedOptions.push(option);
}
}
for (const option of selectedOptions) {
this.selectElem['selectOption'](option);
}
this.selectElem['cd'].detectChanges();
}
}, 500);
}
This is extremely dirty, but it will work until this bug is fixed.
I have used @shireefadel's workaround as follows:
- in the html, add a
#select
on the nb-select element.<nb-select fullWidth multiple formControlName="mySelected" #select>
- Add an angular viewchild to the nb-select element
@ViewChild('select') selectElem: NbSelectComponent;
- Add a workaround timeout and call it when the dynamic content changes
changeMyDynamicContent() { ...Logic here... setTimeout(() => { if (this.selectElem) { const selectedOptions: NbOptionComponent[] = []; for (const option of this.selectElem.options['_results']) { if (mySelected.includes(option.value)) { selectedOptions.push(option); } } for (const option of selectedOptions) { this.selectElem['selectOption'](option); } this.selectElem['cd'].detectChanges(); } }, 500); }
This is extremely dirty, but it will work until this bug is fixed.
thanks, it helped me a lot to create a generic function
static setOptionNbSelect(selectComponent: NbSelectComponent, optToCompare: any) {
setTimeout(() => {
if (selectComponent) {
const selectedOptions: NbOptionComponent[] = [];
for (const option of selectComponent.options['_results']) {
if (_.isEqual(optToCompare, option['value'])) {
selectedOptions.push(option);
break;
}
}
for (const option of selectedOptions) {
selectComponent['selectOption'](option);
}
selectComponent['cd'].detectChanges();
}
}, 500);
}
I hope it works for someone else. Thanks again @ironsm4sh
The issue is still present in nebular 8. Any news? I created a repo nb-select-issue with the issue and the solution proposed by @ironsm4sh and @rardila-uniajc and works fine. Thanks
Still no solution? The workarounds don't seem to work.
This will work
in the html, add a #select on the nb-select element.
<nb-select fullWidth multiple formControlName="mySelected" #select>
in the ts, add a ViewChild to you variables.
@ViewChild('select') selectElem: NbSelectComponent;
add this function
setOptionNbSelect(selectComponent: NbSelectComponent, optToCompare: any) {
if (selectComponent) {
const selectedOptions: NbOptionComponent[] = [];
for (const option of selectComponent.options['_results']) {
if (_.isEqual(optToCompare, option['value'])) {
selectedOptions.push(option);
break;
}
}
for (const option of selectedOptions) {
selectComponent['selectOption'](option);
}
selectComponent['cd'].detectChanges();
}
}
and call the function giving it the default value when you subscribe
this.api.getData().subscribe(
(data:any) =>{
this.setOptionNbSelect(this.selectElem, data.pack);
});
@ettouzany That does not work (or at least it did not for me) if the data is set using Angular's @Input
property.
The issue seems to be due to the component not re-evaluating the selected value when the options are updated dynamically after the value is assigned somehow. My workaround was to clear the value and then set it again with a deferred call (setTimeout with 0 duration) and calling changeDetectorRef.markForCheck().
Something like this:
// preserve value and clear it from component
const value = this.value;
this.value = "";
// deferred code to re-assign value and trigger CD cycle
setTimeout(() => {
this.value = value;
this.changeDetector.markForCheck();
});
I ran into this issue when I was dynamically updating the underlying nb-option
s, while simultaneously changing the selected
value.
<nb-select formControlName="selectedOption">
<nb-option *ngFor="let option of options" [value]="option">{{ option }}</nb-option>
</nb-select>
this.options = newOptions;
this.formGroup.controls.selected.setValue(newOptions[0]);
Solution
I fixed the issue by wrapping the selected
update in a setTimeout
.
this.options = newOptions;
setTimeout(() => this.formGroup.controls.selected.setValue(newOptions[0]));
Explanation
Using formControlName
or ngModel
uses angular's value accessor, which calls writeValue
on the NbSelectComponent
. I believe the problem is that this happens before the underlying options are re-rendered. Basically, the NbSelectComponent
runs compare/matching logic against the wrong (old) NbOption
list, which is why it doesn't show as "selected". Adding a setTimeout
just delays the "selected" change until after the new NbOption
components are updated. Now when it runs, it finds the matching NbOption
and marks it as selected.
Thank you jrasm91, I think this is the best explanation of the problem, took me a while to reach the same conclusion.
I had the same problem but with template based forms, and was able to "solve" it by making sure that the variable that contains the options list for <nb-option>
is set before changing <nb-select>
attached ngModel (Of course mine is not a solution, only a weak workaround)
Explanation Using
formControlName
orngModel
uses angular's value accessor, which callswriteValue
on theNbSelectComponent
. I believe the problem is that this happens before the underlying options are re-rendered. Basically, theNbSelectComponent
runs compare/matching logic against the wrong (old)NbOption
list, which is why it doesn't show as "selected". Adding asetTimeout
just delays the "selected" change until after the newNbOption
components are updated. Now when it runs, it finds the matchingNbOption
and marks it as selected.
The issue seems to be due to the component not re-evaluating the selected value when the options are updated dynamically after the value is assigned somehow. My workaround was to clear the value and then set it again with a deferred call (setTimeout with 0 duration) and calling changeDetectorRef.markForCheck().
Something like this:
// preserve value and clear it from component const value = this.value; this.value = ""; // deferred code to re-assign value and trigger CD cycle setTimeout(() => { this.value = value; this.changeDetector.markForCheck(); });
Works for me, many thanks
Solution I fixed the issue by wrapping the
selected
update in asetTimeout
.this.options = newOptions; setTimeout(() => this.formGroup.controls.selected.setValue(newOptions[0]));
My hero ! this works!
setTimeout(() => {
let correctFormField: AbstractControl = this.userSchemaDataForm.controls['role'];
correctFormField.setValue("admin")
});