实现自动完成

时间:2016-03-09 02:28:21

标签: angular angular2-template angular2-directives

我无法为Angular2找到一个好的自动完成组件。我可以将任何密钥标签对象列表传递给input字段并在import {Component, EventEmitter, Input, Output} from 'angular2/core'; import {Control} from 'angular2/common'; import {Observable} from 'rxjs/Observable'; import {SimpleKeyValue} from '../models/simple-key-value' import 'rxjs/add/operator/map'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; @Component({ selector: 'general-typeahead', template: ` <div> <div class="input-group"> <input type="text" [ngFormControl] = "term" class="form-control" placeholder={{placeHolder}} > </div> <ul> <li class="item" *ngFor="#item of matchingItems" (click)="selectItem(item)"> {{item.value}} </li> </ul> </div>` }) export class GeneralTypeahead { matchingItems: Array<SimpleKeyValue>; term = new Control(); @Input() allItems: Array<SimpleKeyValue>; @Input() placeHolder: string; @Output() onSelectItem = new EventEmitter<SimpleKeyValue>(); constructor() { this.term.valueChanges .distinctUntilChanged() .debounceTime(200) .subscribe((term : string) => this.matchingItems = this.allItems.filter(sl => sl.value.toLowerCase().indexOf(term.toLowerCase()) > -1)); } selectItem(sl: SimpleKeyValue) { this.onSelectItem.emit(sl); } } 字段上自动完成。

Kendo尚不支持Angular 2,而且它是我们内部主要使用的。 Angular Material还没有支持Angular 2。

任何人都可以指出我正确的方向或让我知道他们正在使用什么?

这是我迄今为止所建立的。这很糟糕,我想找一些看起来不错的东西。

lodash

8 个答案:

答案 0 :(得分:121)

更新:这个答案促成了ng2-completer Angular2自动完成组件的开发。 这是Angular2的现有自动完成组件列表:

  1. ng2-completer
  2. ng2-auto-complete
  3. ng2-typeahead
  4. 感谢@ dan-cancro提出这个想法

    为那些希望创建自己的指令的人保留原始答案:

    要显示自动填充列表,我们首先需要一个attribute directive,它将根据输入文本返回建议列表,然后在下拉列表中显示它们。 该指令有2个选项来显示列表:

    1. 获取对nativeElement的引用并直接操作DOM
    2. 使用DynamicComponentLoader
    3. 动态加载列表组件

      在我看来,第二种方式是更好的选择,因为它使用角度2核心机制而不是通过直接使用DOM来绕过它们,因此我将使用这种方法。

      这是指令代码:

      "use strict";
      import {Directive, DynamicComponentLoader, Input, ComponentRef, Output, EventEmitter, OnInit, ViewContainerRef} from "@angular/core";
      import {Promise} from "es6-promise";
      import {AutocompleteList} from "./autocomplete-list";
      
      @Directive({
          selector: "[ng2-autocomplete]", // The attribute for the template that uses this directive
          host: {
              "(keyup)": "onKey($event)" // Liten to keyup events on the host component
          }
      })
      export class AutocompleteDirective implements OnInit {
          // The search function should be passed as an input
          @Input("ng2-autocomplete") public search: (term: string) => Promise<Array<{ text: string, data: any }>>;
          // The directive emits ng2AutocompleteOnSelect event when an item from the list is selected
          @Output("ng2AutocompleteOnSelect") public selected = new EventEmitter();
      
          private term = "";
          private listCmp: ComponentRef<AutocompleteList> = undefined;
          private refreshTimer: any = undefined;
          private searchInProgress = false;
          private searchRequired = false;
      
          constructor( private viewRef: ViewContainerRef, private dcl: DynamicComponentLoader) { }
          /**
           * On key event is triggered when a key is released on the host component
           * the event starts a timer to prevent concurrent requests
           */
          public onKey(event: any) {
              if (!this.refreshTimer) {
                  this.refreshTimer = setTimeout(
                  () => {
                      if (!this.searchInProgress) {
                          this.doSearch();
                      } else {
                          // If a request is in progress mark that a new search is required
                          this.searchRequired = true;
                      }
                  },
                  200);
              }
              this.term = event.target.value;
              if (this.term === "" && this.listCmp) {
                  // clean the list if the search term is empty
                  this.removeList();
              }
          }
      
          public ngOnInit() {
              // When an item is selected remove the list
              this.selected.subscribe(() => {
                  this.removeList();
              });
          }
      
          /**
           * Call the search function and handle the results
           */
          private doSearch() {
              this.refreshTimer = undefined;
              // if we have a search function and a valid search term call the search
              if (this.search && this.term !== "") {
                  this.searchInProgress = true;
                  this.search(this.term)
                  .then((res) => {
                      this.searchInProgress = false;
                      // if the term has changed during our search do another search
                      if (this.searchRequired) {
                          this.searchRequired = false;
                          this.doSearch();
                      } else {
                          // display the list of results
                          this.displayList(res);
                      }
                  })
                  .catch(err => {
                      console.log("search error:", err);
                      this.removeList();
                  });
              }
          }
      
          /**
           * Display the list of results
           * Dynamically load the list component if it doesn't exist yet and update the suggestions list
           */
          private displayList(list: Array<{ text: string, data: any }>) {
              if (!this.listCmp) {
                  this.dcl.loadNextToLocation(AutocompleteList, this.viewRef)
                  .then(cmp => {
                      // The component is loaded
                      this.listCmp = cmp;
                      this.updateList(list);
                      // Emit the selectd event when the component fires its selected event
                      (<AutocompleteList>(this.listCmp.instance)).selected
                          .subscribe(selectedItem => {
      
                          this.selected.emit(selectedItem);
                      });
                  });
              } else {
                  this.updateList(list);
              }
          }
      
          /**
           * Update the suggestions list in the list component
           */
          private updateList(list: Array<{ text: string, data: any }>) {
              if (this.listCmp) {
                  (<AutocompleteList>(this.listCmp.instance)).list = list;
              }
          }
      
          /**
           * remove the list component
           */
          private removeList() {
              this.searchInProgress = false;
              this.searchRequired = false;
              if (this.listCmp) {
                  this.listCmp.destroy();
                  this.listCmp = undefined;
              }
          }
      }
      

      该指令动态加载下拉组件,这是使用bootstrap 4的这样一个组件的示例:

      "use strict";
      import {Component, Output, EventEmitter} from "@angular/core";
      
      @Component({
          selector: "autocomplete-list",
          template: `<div class="dropdown-menu  search-results">
                          <a *ngFor="let item of list" class="dropdown-item" (click)="onClick(item)">{{item.text}}</a>
                     </div>`, // Use a bootstrap 4 dropdown-menu to display the list
          styles: [".search-results { position: relative; right: 0; display: block; padding: 0; overflow: hidden; font-size: .9rem;}"]
      })
      export class AutocompleteList  {
          // Emit a selected event when an item in the list is selected
          @Output() public selected = new EventEmitter();
      
          public list;
      
          /**
           * Listen for a click event on the list
           */
          public onClick(item: {text: string, data: any}) {
              this.selected.emit(item);
          }
      }
      

      要在另一个组件中使用该指令,您需要导入该指令,将其包含在components指令中,并为其提供搜索功能和事件处理程序:

       "use strict";
      import {Component} from "@angular/core";
      
      import {AutocompleteDirective} from "../component/ng2-autocomplete/autocomplete";
      
      @Component({
          selector: "my-cmp",
          directives: [AutocompleteDirective],
          template: `<input class="form-control" type="text" [ng2-autocomplete]="search()" (ng2AutocompleteOnSelect)="onItemSelected($event)" autocomplete="off">`
      })
      export class MyComponent  {
      
          /**
           * generate a search function that returns a Promise that resolves to array of text and optionally additional data
           */  
          public search() {
              return (filter: string): Promise<Array<{ text: string, data: any }>> => {
                  // do the search
                  resolve({text: "one item", data: null});
              };
          }
      
          /**
           * handle item selection
           */  
          public onItemSelected(selected: { text: string, data: any }) {
              console.log("selected: ", selected.text);
          }
      }
      

      更新代码与angular2 rc.1

      兼容

答案 1 :(得分:20)

PrimeNG具有原生AutoComplete组件,具有模板和多选等高级功能。

http://www.primefaces.org/primeng/#/autocomplete

答案 2 :(得分:2)

我认为您可以使用typeahead.js。它有打字稿定义。因此,如果您使用打字稿进行开发,我会很容易使用它。

答案 3 :(得分:1)

我知道你已经有了几个答案,但我处于类似的情况,我的团队并不想依赖于繁重的库或任何与bootstrap相关的东西,因为我们使用的是材料所以我自己完成了自动完成控制,使用类似材料的样式,你可以使用我的autocomplete或者至少你可以看看给你一些guiadance,关于如何上传你的组件以在NPM上共享的简单示例没有太多文档。

答案 4 :(得分:0)

我为anuglar2自动完成创建了一个模块 在此模块中,您可以使用数组或URL npm link:ang2-autocomplete

答案 5 :(得分:0)

可在 angular-material

中自动完成的内置组件

点击here to check it

注意:您甚至可以根据自己的要求自定义此自动填充的过滤逻辑。

答案 6 :(得分:0)

我已经构建了一个相当简单,可重用且功能强大的Angular2自动完成组件,它基于本答案/其他有关此主题和其他主题的教程中的一些想法。它并不全面,但如果您决定建立自己的,可能会有所帮助。

组件:

import { Component, Input, Output, OnInit, ContentChild, EventEmitter, HostListener } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { AutoCompleteRefDirective } from "./autocomplete.directive";

@Component({
    selector: 'autocomplete',
    template: `
<ng-content></ng-content>
<div class="autocomplete-wrapper" (click)="clickedInside($event)">
    <div class="list-group autocomplete" *ngIf="results">
        <a [routerLink]="" class="list-group-item" (click)="selectResult(result)" *ngFor="let result of results; let i = index" [innerHTML]="dataMapping(result) | highlight: query" [ngClass]="{'active': i == selectedIndex}"></a>
    </div>
</div>
    `,
    styleUrls: ['./autocomplete.component.css']
})
export class AutoCompleteComponent implements OnInit {

    @ContentChild(AutoCompleteRefDirective)
    public input: AutoCompleteRefDirective;

    @Input() data: (searchTerm: string) => Observable<any[]>;
    @Input() dataMapping: (obj: any) => string;
    @Output() onChange = new EventEmitter<any>();

    @HostListener('document:click', ['$event'])
    clickedOutside($event: any): void {
        this.clearResults();
    }

    public results: any[];
    public query: string;
    public selectedIndex: number = 0;
    private searchCounter: number = 0;

    ngOnInit(): void {
        this.input.change
            .subscribe((query: string) => {
                this.query = query;
                this.onChange.emit();
                this.searchCounter++;
                let counter = this.searchCounter;

                if (query) {
                    this.data(query)
                        .subscribe(data => {
                            if (counter == this.searchCounter) {
                                this.results = data;
                                this.input.hasResults = data.length > 0;
                                this.selectedIndex = 0;
                            }
                        });
                }
                else this.clearResults();
            });

        this.input.cancel
            .subscribe(() => {
                this.clearResults();
            });

        this.input.select
            .subscribe(() => {
                if (this.results && this.results.length > 0)
                {
                    this.selectResult(this.results[this.selectedIndex]);
                }
            });

        this.input.up
            .subscribe(() => {
                if (this.results && this.selectedIndex > 0) this.selectedIndex--;
            });

        this.input.down
            .subscribe(() => {
                if (this.results && this.selectedIndex + 1 < this.results.length) this.selectedIndex++;
            });
    }

    selectResult(result: any): void {
        this.onChange.emit(result);
        this.clearResults();
    }

    clickedInside($event: any): void {
        $event.preventDefault();
        $event.stopPropagation();
    }

    private clearResults(): void {
        this.results = [];
        this.selectedIndex = 0;
        this.searchCounter = 0;
        this.input.hasResults = false;
    }
}

组件CSS:

.autocomplete-wrapper {
    position: relative;
}

.autocomplete {
    position: absolute;
    z-index: 100;
    width: 100%;
}

指令:

import { Directive, Input, Output, HostListener, EventEmitter } from '@angular/core';

@Directive({
    selector: '[autocompleteRef]'
})
export class AutoCompleteRefDirective {
    @Input() hasResults: boolean = false;
    @Output() change = new EventEmitter<string>();
    @Output() cancel = new EventEmitter();
    @Output() select = new EventEmitter();
    @Output() up = new EventEmitter();
    @Output() down = new EventEmitter();

    @HostListener('input', ['$event'])
    oninput(event: any) {
        this.change.emit(event.target.value);
    }

    @HostListener('keydown', ['$event'])
    onkeydown(event: any)
    {
        switch (event.keyCode) {
            case 27:
                this.cancel.emit();
                return false;
            case 13:
                var hasResults = this.hasResults;
                this.select.emit();
                return !hasResults;
            case 38:
                this.up.emit();
                return false;
            case 40:
                this.down.emit();
                return false;
            default:
        }
    }
}

高亮管:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'highlight'
})

export class HighlightPipe implements PipeTransform {
    transform(value: string, args: any): any {
        var re = new RegExp(args, 'gi');

        return value.replace(re, function (match) {
            return "<strong>" + match + "</strong>";
        })

    }
}

实施:

import { Component } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { Subscriber } from "rxjs/Subscriber";

@Component({
    selector: 'home',
    template: `
<autocomplete [data]="getData" [dataMapping]="dataMapping" (onChange)="change($event)">
    <input type="text" class="form-control" name="AutoComplete" placeholder="Search..." autocomplete="off" autocompleteRef />
</autocomplete>
    `
})
export class HomeComponent {

    getData = (query: string) => this.search(query);

    // The dataMapping property controls the mapping of an object returned via getData.
    // to a string that can be displayed to the use as an option to select.
    dataMapping = (obj: any) => obj;

    // This function is called any time a change is made in the autocomplete.
    // When the text is changed manually, no object is passed.
    // When a selection is made the object is passed.
    change(obj: any): void {
        if (obj) {
            // You can do pretty much anything here as the entire object is passed if it's been selected.
            // Navigate to another page, update a model etc.
            alert(obj);
        }
    }

    private searchData = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];

    // This function mimics an Observable http service call.
    // In reality it's probably calling your API, but today it's looking at mock static data.
    private search(query: string): Observable<any>
    {
        return new Observable<any>((subscriber: Subscriber<any>) => subscriber
            .next())
            .map(o => this.searchData.filter(d => d.indexOf(query) > -1));
    }
}

答案 7 :(得分:0)

我想添加尚未有人提及的内容:ng2-input-autocomplete

NPM:https://www.npmjs.com/package/ng2-input-autocomplete

GitHub:https://github.com/liuy97/ng2-input-autocomplete#readme