Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NbSelect doesn't display the [selected] option if data is dynamic #2145

Open
1 of 2 tasks
meisbest opened this issue Dec 28, 2019 · 21 comments · May be fixed by #2955
Open
1 of 2 tasks

NbSelect doesn't display the [selected] option if data is dynamic #2145

meisbest opened this issue Dec 28, 2019 · 21 comments · May be fixed by #2955

Comments

@meisbest
Copy link

meisbest commented Dec 28, 2019

Issue type

I'm submitting a ... (check one with "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
@augustocb23
Copy link

Same here. If I use the standard HTML select, it updates without problems.

@michabbb
Copy link

until it´s fixed: setting "selected" in ngAfterViewInit works for me....

@fofiedelly
Copy link

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]],....

@gonzalo42
Copy link

any news on this?

@gonzalo42
Copy link

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

@cacc559230
Copy link

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 !

@alex88
Copy link

alex88 commented Nov 29, 2020

Is there any workaround on this?

@michabbb
Copy link

@alex88 doesn´t this work for you?

@alex88
Copy link

alex88 commented Nov 30, 2020

@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)

@shireefadel
Copy link

shireefadel commented Dec 13, 2020

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; }, ));

@ironsm4sh
Copy link

I have used @shireefadel's workaround as follows:

  1. in the html, add a #select on the nb-select element.
<nb-select fullWidth multiple formControlName="mySelected" #select>
  1. Add an angular viewchild to the nb-select element
@ViewChild('select') selectElem: NbSelectComponent;
  1. 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.

@rardila-uniajc
Copy link

I have used @shireefadel's workaround as follows:

  1. in the html, add a #select on the nb-select element.
<nb-select fullWidth multiple formControlName="mySelected" #select>
  1. Add an angular viewchild to the nb-select element
@ViewChild('select') selectElem: NbSelectComponent;
  1. 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

@visyone
Copy link
Contributor

visyone commented Sep 15, 2021

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

@MincDev
Copy link

MincDev commented Dec 28, 2021

Still no solution? The workarounds don't seem to work.

@ettouzany
Copy link

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);
          });

@ironsm4sh
Copy link

@ettouzany That does not work (or at least it did not for me) if the data is set using Angular's @Input property.

@colinvella
Copy link

colinvella commented Jun 30, 2022

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();
});

@jrasm91
Copy link

jrasm91 commented Jul 22, 2022

I ran into this issue when I was dynamically updating the underlying nb-options, 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.

@mdefelicegenio
Copy link

mdefelicegenio commented Nov 11, 2022

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

@ddt313
Copy link

ddt313 commented Dec 20, 2022

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

@nikita-fuchs
Copy link

Solution I fixed the issue by wrapping the selected update in a setTimeout.

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")
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.