Bind an Object instead of string in ngx-bootstrap Typeahead in Angular 2+
Asked Answered
M

2

8

I am using this link for implementing a Typeahead. Now I am using TypeaheadOptionField to display a name on typeahead but it also binds the name string in the model. I want to bind the Object instead of the string.

My HTML Code:

<input formControlName="item"  class="form-control" [typeahead]="allItemsArray" [typeaheadItemTemplate]="customItemTemplate"
          [typeaheadOptionsLimit]="7" [typeaheadMinLength]="0" [typeaheadOptionField]="name" (typeaheadOnSelect)="onSelectItem($event)">

allItemsArray:

[
    {
        name: 'a',
        code: '12'
    },
    {
        name: 'b',
        code: '13'
    }
]

Value bound to form control: 'a'
Required value: {'name': 'a', 'code': '12'}

One thing I tried is implementing an event which sets the model value as object but it didn't work.

Mccomas answered 12/2, 2018 at 6:2 Comment(6)
can you please explain it a bit better you want to bind to a array of objects ? then stringfy it if you want something like thisFredel
I want to bind to Object but the typeaheadOptionField property doesn't let me. It binds the model with item.name which is a string. I want to bind model with the Object.Mccomas
You can use PrimeNg Dropdown primefaces.org/primeng/#/dropdownRufina
I know there are other options like primeNg and ng2-typeahead, but I am curious about this one.Mccomas
I think you should use {id:string, text:string}. Could you give me an example of your allItemsArray?Obstacle
Let's say allItemsArray have two values {name:string, code:string}.Mccomas
S
1

I'm having the exact same problem right now.

When the typeahead is used with the Angular Form API, it uses the value found for the key passed in typeaheadOptionField. This is imho a bug or at least it should be configurable.

What I'm doing now is wrapping the input with a custom control and using the Output typeaheadOnSelect which is called when an option of the typeahead is selected. Within the event data you can find the entire object. But you have to handle the control data management by yourself.

At least for now, I couldn't find another solution.

Edit:

Here's my code (removed all abstractions, no warranty that it works):

@Component({
  selector: 'my-typeahead-control',
  templateUrl: './my-typeahead-control.html',
  providers: [
    {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MyTypeaheadControl), multi: true}
  ]
})
export class MyTypeaheadControl implements ControlValueAccessor
{
  // -- -- -- -- -- -- -- -- -- -- typeahead data -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

  @Input()
  public items:any[] | Observable<any[]> = [];

  @Input()
  public itemLabelKey:string;

  // -- -- -- -- -- -- -- -- -- -- internal data -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

  public selectedItemLabel:string;

  // -- -- -- -- -- -- -- -- -- -- interface implementation -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

  public writeValue(obj:any):void
  {
    this.updateSelectedItemLabel(obj);
  }

  private onChange:Function;

  public registerOnChange(fn:any):void
  {
    this.onChange = fn;
  }

  private onTouch:Function;

  public registerOnTouched(fn:any):void
  {
    this.onTouch = fn;
  }

  public setDisabledState(isDisabled:boolean):void
  {
    // ...
  }

  // -- -- -- -- -- -- -- -- -- -- control data handling -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

  public onSelect(event:TypeaheadMatch):void
  {
    this.onTouch();
    this.onChange(event.item);
    this.updateSelectedItemLabel(event.item);
  }

  private updateSelectedItemLabel(obj:any):void
  {
    this.selectedItemLabel = (this.itemLabelKey) ? _.get(obj, this.itemLabelKey) : obj;
  }
}

And the template:

<input [ngModel]="selectedItemLabel"

       [typeahead]="items"
       [typeaheadOptionField]="itemLabelKey"
       [typeaheadMinLength]="0"
       [container]="'body'"

       (typeaheadOnSelect)="onSelect($event)">

Now it can be used as follows:

    <my-typeahead-control formControlName="item" [items]="allItemsArray" itemLabelKey="name"></my-typeahead-control>
Sophist answered 14/2, 2018 at 9:38 Comment(0)
O
1

Here's basically how I worked around this problem with Angular 7 and NGX-Bootstrap 3.

HTML

<input
  [formControl]="myTypeahead"
  [typeahead]="filteredOpts"
  typeaheadOptionField="value"
  (typeaheadOnSelect)="select($event.item)"/>

TypeScript

interface Opt {
  value: string;
  key: string;
}

export class MyComp implements OnInit {
  myTypeahead = new FormControl();
  options: Opts[];
  filteredOpts: Opts[] = [];
  selectedOption: Opt;

  constructor() {}

  ngOnInit() {
    this.myTypeahead.valueChanges.pipe(startWith(''))
      .subscribe((value) => {
        this.filteredOpts = this._filter(value));
  }

  private _filter(value: string): Opt[] {
    return this.options
      .filter((opt: Opt) => opt.value.toLowerCase().includes(value.toLowerCase()));
  }

  select(opt: Opt) {
    this.selectedOption = opt;
  }
}

Based on the Angular Material Autocomplete custom filter example found here: https://material.angular.io/components/autocomplete/overview

My case was slightly more complicated in that I was loading options with an API call in ngOnInit, but that was no issue.

Also note that this is basically doing the work of the typeahead in _filter and is not the most efficient method. However it got me moving forward again.

Oubre answered 2/4, 2019 at 15:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.