Search code examples
javascriptangulartypescriptobservablengx-bootstrap

using ngx-bootstrap typeahead in angular 5 with an array of model values


HTML Template

<tr *ngFor="let wi of page.items" (click)="selectWorkItem(wi)">
  <td><input [ngModel]="wi.checked" type="checkbox"></td>
  <td [textContent]="wi.someText"></td>
  <td>
     <input 
        class="form-control"
        tabindex="-1"
        [typeahead]="WHAT THE ?" <!-- tried propertyManagers below -->
        [ngModel]="wi.propertyManager" />

Component

export class WorkItemListComponent implements OnInit {

    selectedPropertyManager: any;
    propertyManagers = Observable.create((observer: any) => {
        observer.next(this.selectedPropertyManager);
    }).mergeMap((token: string) => 
         this.partyService.loadPartyHints(token).map(_ => _.displayName));

page.items contains a list of items that correspond to each row in the table. What I am observing, is that in my case the observer.next is meant to be bound to the [ngModel] of the page.items[?].propertyManager, but the observable is actually bound to selectedPropertyManager (which is NOT the same as the [ngModel]).

Is there any way to create a typeahead that observes the current model value, and passes that to the loadPartyHints function.

<input 
    class="form-control"
    tabindex="-1"
    [typeahead]="CREATE_OBSERVABLE_OVER_MODEL(wi.propertyManager)"
    [ngModel]="wi.propertyManger" 
    <!-- THIS is the model value, not selectedPropertyManager -->

Edit

I have tried this...

With a template like this...

<input 
   #inp
   class="form-control"
   tabindex="-1"
   [typeahead]="propertyManagers"
   [(ngModel)]="wi.propertyManager"
   (ngModelChange)="valueChanged(inp.value)"/>

and the following Subject

hints = new Subject();
propertyManagers = this.hints.mergeMap((token: string) => this.partyService.loadPartyHints(token).map(_ => _.map(x => x.displayName)));

valueChanged(value: string) {
    this.logger.info(`input changed :: ${value}`);
    this.hints.next(value);
}

This get's me closer, but now all the ngx-bootstrap typeahead(s) are interfering with each other and getting each other's data.

I need to someone create a Subject / Observable factory that wires up each row independently?


Solution

  • import { Component, Provider, forwardRef, Input, Output, EventEmitter } from "@angular/core";
    import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
    import { NGXLogger } from "ngx-logger";
    import { Subject } from "rxjs/Subject";
    
    @Component({
        selector: "typeahead-input",
        template: `<input 
            #inp
            class="form-control"
            tabindex="-1"
            [typeahead]="observable"
            [(ngModel)]="value"
            (typeaheadOnSelect)="select($event)"
            (ngModelChange)="hintValueChanged(inp.value)"/>
        `,
        providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                useExisting: forwardRef(() => TypeaheadInputComponent),
                multi: true
            }]
    })
    export class TypeaheadInputComponent implements ControlValueAccessor {
        private _value: any = "";
        hints = new Subject();
        observable = this.hints.mergeMap((token: string) => this.loadHints(token));
        @Input() hint: Function;
        @Output() itemSelected = new EventEmitter<string>();
    
        constructor(
            private readonly logger: NGXLogger) {
        }
    
        get value(): any { return this._value; };
    
        set value(v: any) {
            if (v !== this._value) {
                this._value = v;
                this.onChange(v);
            }
        }
    
        loadHints(token: string) {
            this.logger.info(`loading hints ${token}`);
            return this.hint(token);
        }
    
        hintValueChanged(hint: string) {
            this.logger.info(`typehead :: hint value (${hint})`);
            this.hints.next(hint);
        }
    
        select(selectedItem: any) {
            this.itemSelected.emit(selectedItem.value);
        }
    
        writeValue(value: any) {
            this._value = value;
            this.onChange(value);
        }
    
        onChange = (_) => { };
        onTouched = () => { };
        registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
        registerOnTouched(fn: () => void): void { this.onTouched = fn; }
    }