Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 248
Pratik Gandhi Avatar asked Feb 12 '18 06:02

Pratik Gandhi


2 Answers

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>
like image 134
Manuel Avatar answered Nov 14 '22 14:11

Manuel


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.

like image 20
Brad Avatar answered Nov 14 '22 13:11

Brad