Search code examples
typescriptangularautocompletetype-ahead

Angular 2 AutoComplete not working properly


[When I type in inputs(list of countries), the autocomplete drops down a list of countries that matches my input. But the problem is I can't select a country from that list using mouse click][1]

I'm using angular2 and while doing a typeahead(autocomplete), i'm struck with this error

//app.component.js

    enter code here

import {Component, ElementRef} from 'angular2/core';

@Component({
    selector: 'my-app',
    host: {
        '(document:click)': 'handleClick($event)',
    },
    template: `
        <div class="container" >
            <div class="input-field col s12">
              <input id="country" type="text" class="validate filter-input" [(ngModel)]=query (keyup)=filter($event)  (blur)=handleBlur()>
              <label for="country">Country</label>
            </div>
            <div class="suggestions" *ngIf="filteredList.length > 0">
                <ul *ngFor="#item of filteredList;#idx = index" >
                    <li [class.complete-selected]="idx == selectedIdx">
                        <a (click)="select(item)">{{item}}</a>
                    </li>
                </ul>
            </div>
        </div>
        `
})

export class AppComponent {
    public query = '';
    public countries = ["Albania", "Andorra", "Armenia", "Austria", "Azerbaijan", "Belarus", "Belgium", "Bosnia & Herzegovina",
        "Bulgaria", "Croatia", "Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Georgia",
        "Germany", "Greece", "Hungary", "Iceland", "Ireland", "Italy", "Kosovo", "Latvia", "Liechtenstein",
        "Lithuania", "Luxembourg", "Macedonia", "Malta", "Moldova", "Monaco", "Montenegro", "Netherlands",
        "Norway", "Poland", "Portugal", "Romania", "Russia", "San Marino", "Serbia", "Slovakia",
        "Slovenia", "Spain", "Sweden", "Switzerland", "Turkey", "Ukraine", "United Kingdom", "Vatican City"];
    public filteredList = [];
    public elementRef;
    selectedIdx: number;

    constructor(myElement: ElementRef) {
        this.elementRef = myElement;
        this.selectedIdx = -1;
    }

    filter(event: any) {
        if (this.query !== "") {
            this.filteredList = this.countries.filter(function (el) {
                return (el.toLowerCase().substr(0,this.query.length) === this.query.toLowerCase()) == true;
            }.bind(this));
            if (event.code == "ArrowDown" && this.selectedIdx < this.filteredList.length) {
                this.selectedIdx++;
            } else if (event.code == "ArrowUp" && this.selectedIdx > 0) {
                this.selectedIdx--;
            }
        } else {
            this.filteredList = [];
        }
    }

    select(item) {
        this.query = item;
        this.filteredList = [];
        this.selectedIdx = -1;
    }

    handleBlur() {
        if (this.selectedIdx > -1) {
            this.query = this.filteredList[this.selectedIdx];
        }
        this.filteredList = [];
        this.selectedIdx = -1;
    }

    handleClick(event) {
        var clickedComponent = event.target;
        var inside = false;
        do {
            if (clickedComponent === this.elementRef.nativeElement) {
                inside = true;
            }
            clickedComponent = clickedComponent.parentNode;
        } while (clickedComponent);
        if `enter code here`(!inside) {
            this.filteredList = [];
        }
        this.selectedIdx = -1;
    }


}`

____________________________________________________________________________________________________________________________________________________
//main.ts
import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'

bootstrap(AppComponent);

____________________________________________________________________________________________________________________________________________________
//index.html
<html>
  <head>
    <title>MasaCafe</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    

    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/systemjs/dist/system-polyfills.js"></script>

    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="node_modules/rxjs/bundles/Rx.js"></script>
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>

    <script src="https://code.jquery.com/jquery-2.2.1.min.js" ></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>

    <link rel="stylesheet" href="style.css"></link>

    <!-- 2. Configure SystemJS -->
    <script>
      System.config({
        packages: {        
          app: {
            format: 'register',
            defaultExtension: 'js'
          }
        }
      });
      System.import('app/main')
            .then(null, console.error.bind(console));
    </script>

  </head>

  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>

</html>


What did I do wrong?


  [1]: https://i.sstatic.net/i06n3.png

Solution

    1. Your input element has blur handler which takes precedence (my understanding) over click event on a element with in li element. The moment you click on the auto suggestions, the browser will trigger onblur event which will reset the filteredList in your AppComponent.

    2. Resetting the filteredList will remove the auto suggestion list from the dom since you used *ngIf in the template thus voiding the click event. So the click event will never be invoked.

    3. Try removing the blur handler and this should work as your expected. I mean in terms of clicking the auto suggestion item.

    4. I suggest to use (mousedown) instead of (click) on anchor element. Also in the mousedown event handler use event.stopPropagation so your autosuggestions list will not loose focus.

        select(event,item) {
          this.query = item;
          this.filteredList = [];
          this.selectedIdx = -1;
          event.stopPropagation();
        }
      

    <div class="container">
      <div class="input-field col s12">
        <input id="country" type="text" class="validate filter-input" [(ngModel)]=query (keyup)=filter($event) (blur)="handleBlur()">
        <div class="suggestions" *ngIf="filteredList.length > 0">
          <ul>
            <li *ngFor="let item of filteredList;let idx = index" [class.complete-selected]="idx == selectedIdx"
                (mousedown)="select($event,item)">
              {{item}}
            </li>
          </ul>
        </div>
      </div>
      <label for="country">Country</label>
    </div>

    Hope this helps.