Search code examples
angularng-bootstrapviewchild

Angular 9: More than one ngb-typeahead in one form?


I am using the NgbTypeahead component from NG-Boostrap 6.0.3 in my Angular 9 app. The first one works fine, but my form has quite a few.

Do I need to add a separate ViewChild and a separate search function for each one of them? Or am I missing something? Can someone point me to an example with more than one?

For reference, here's a Stackblitz with just the one.


Solution

  • well, really my comment is NOT correct, imagine you has 2 ngbTypeHead You need that focus$ and click$ was an array, for this, you can use map, some like

    focus$ = [0,1].map(_=>new Subject<string>());
    click$ = [0,1].map(_=>new Subject<string>());
    

    Well, you can also too make some like (I use a fool array and map) but it is the same than:

    focus$ = [new Subject<string>(),new Subject<string>()];
    

    I use an array to the model

    model: any[]=[];
    

    And change the searchFunction that received as parameters: an index, a instance and a term (the index is necesary to make reference to the subjects

    searchType(index,instance, text$) {
        return (text$: Observable<string>) => {
          const debouncedText$ = text$.pipe(
            debounceTime(200),
            distinctUntilChanged()
          );
          const clicksWithClosedPopup$ = this.click$[index].pipe(
            filter(() => !instance.isPopupOpen())
          );
          const inputFocus$ = this.focus$[index];
    
          return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
            map(term =>
              (term === ""
                ? states
                : states.filter(
                    v => v.toLowerCase().indexOf(term.toLowerCase()) > -1
                  )
              ).slice(0, 10)
            )
          );
        };
      }
    

    Then, the only we need is change our ngbTypeahead

      <input
            ...
      [(ngModel)]="model[index]"
      [ngbTypeahead]="searchType(i,instance,$text)"
      (focus)="focus$[i].next($any($event).target.value)"
      (click)="click$[i].next($any($event).target.value)"
      #instance="ngbTypeahead"
      >
    

    You can see an example in stackblitz

    Update if we need differents data, we can improve the function search passing the "data", so, if we add a new parameter to search:

    searchType(index,instance, data,text$) { //<--pass "data"
        return (text$: Observable<string>) => {
          const debouncedText$ = text$.pipe(
            debounceTime(200),
            distinctUntilChanged()
          );
          const clicksWithClosedPopup$ = this.click$[index].pipe(
            filter(() => !instance.isPopupOpen())
          );
          const inputFocus$ = this.focus$[index];
    
          return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
            map(term =>
              (term === ""
                ? data          //<--here use data
                : data.filter(  //<--here use data too
                    v => v.toLowerCase().indexOf(term.toLowerCase()) > -1
                  )
              ).slice(0, 10)
            )
          );
        };
      }
    

    We can change the call and write:

    [ngbTypeahead]="searchType(i,instance,states,$text)"
    

    Another option is, according to the "index" search in one or another array, so the function becomes like

    searchType(index,instance, text$) { 
        const data=index==0?states:this.fruits; //<--the data according the index
        return (text$: Observable<string>) => {
          const debouncedText$ = text$.pipe(
            debounceTime(200),
            distinctUntilChanged()
          );
          const clicksWithClosedPopup$ = this.click$[index].pipe(
            filter(() => !instance.isPopupOpen())
          );
          const inputFocus$ = this.focus$[index];
    
          return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
            map(term =>
              (term === ""
                ? data          //<--here use data
                : data.filter(  //<--here use data too
                    v => v.toLowerCase().indexOf(term.toLowerCase()) > -1
                  )
              ).slice(0, 10)
            )
          );
        };
      }