page change angular angular2-template angular2-directives

angular - change - Implementando autocompletar



page title angular 4 (8)

Creo que puedes usar typeahead.js . Hay definiciones mecanografiadas para ello. así que será fácil de usar, supongo que si está utilizando el mecanografiado para el desarrollo.

Tengo problemas para encontrar un buen componente de autocompletado para Angular2. Cualquier cosa a la que pueda pasarle una lista de objetos de etiqueta clave y tener un buen autocompletado en un campo de input .

Kendo todavía no es compatible con Angular 2 y es lo que más usamos internamente. Tampoco parece que Angular Material sea compatible con Angular 2.

¿Alguien puede señalarme en la dirección correcta o decirme qué están usando?

Esto es lo que construí hasta ahora. Es bastante malo y me gustaría encontrar algo que se vea bien.

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); } }


He creado un componente de autocompletado Angular2 bastante simple, reutilizable y funcional basado en algunas de las ideas de esta respuesta / otros tutoriales sobre este tema y otros. De ninguna manera es exhaustivo, pero puede ser útil si decide crear el suyo propio.

El componente:

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; } }

El componente CSS:

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

La directiva:

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: } } }

El tubo destacado:

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>"; }) } }

La implementación:

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)); } }


He creado un módulo para autocompletar anuglar2. En este módulo puede usar la matriz o el enlace url npm: ang2-autocomplete




Sé que ya tiene varias respuestas, pero estaba en una situación similar en la que mi equipo no quería depender de bibliotecas pesadas ni nada relacionado con el arranque ya que estamos usando material, así que hice nuestro propio control de autocompletar, usando material similar estilos, puedes usar mi autocomplete o al menos puedes echarle un vistazo para guiarte, no había mucha documentación sobre ejemplos simples sobre cómo cargar tus componentes para compartirlos en NPM.


Un componente incorporado disponible para autocompletar en material angular

Haga clic aquí para verificarlo !

Nota: incluso puede personalizar la lógica de filtro de este autocompletado según sus requisitos.


Actualización: esta respuesta ha llevado al desarrollo de ng2-completer un componente de autocompletado Angular2. Esta es la lista de componentes autocompletados existentes para Angular2:

  1. ng2-completer
  2. ng2-auto-complete
  3. ng2-typeahead

El crédito va a @ dan-cancro por tener la idea

Mantener la respuesta original para aquellos que desean crear su propia directiva:

Para mostrar la lista de autocompletado, primero necesitamos una directiva de atributos que devolverá la lista de sugerencias basadas en el texto de entrada y luego las mostrará en un menú desplegable. La directiva tiene 2 opciones para mostrar la lista:

  1. Obtenga una referencia al nativeElement y manipule el DOM directamente
  2. Cargue dinámicamente un componente de lista usando DynamicComponentLoader

Me parece que la segunda forma es una mejor opción, ya que utiliza mecanismos angulares de 2 núcleos en lugar de eludirlos trabajando directamente con el DOM y, por lo tanto, usaré este método.

Este es el código de la directiva:

"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; } } }

La directiva carga dinámicamente un componente desplegable, esta es una muestra de dicho componente usando 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); } }

Para usar la directiva en otro componente, debe importar la directiva, incluirla en las directivas de componentes y proporcionarle una función de búsqueda y un controlador de eventos para la selección:

"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); } }

Actualización: código compatible con angular2 rc.1