我已经根据这个答案/其他关于这个主题和其他主题的教程中的一些想法构建了一个相当简单、可重用且功能强大的 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));
}
}