Angular 2 Autocomplete

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
0
down vote
favorite
I've tried to build a fairly simple, reusable and functional Angular2 autocomplete component.
The component:
import Component, Input, Output, OnInit, ContentChild, EventEmitter, HostListener from '@angular/core';
import Observable from "rxjs/Observable";
import AutoCompleteRefDirective from "./autocomplete.directive";
@Component(
selector: 'autocomplete',
templateUrl: './autocomplete.component.html',
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;
The component HTML:
<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>
The component CSS:
.autocomplete-wrapper
position: relative;
.autocomplete
position: absolute;
z-index: 100;
width: 100%;
The directive:
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:
The highlight pipe:
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>";
)
The implementation:
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));
A few thoughts:
- The use of both a Component and a Directive isn't ideal but I couldn't figure out a better way to do it.
- The
getDataproperty andsearch()functions have been separated so as to mimic a real life service call scenario. It took me an age working out how to wire this up using a property and=>! But is there a better way, especially when dealing with async calls? - The highlight pipe isn't necessary but it does improve the experience so I figured I'd include it here.
- I'm leveraging the bootstrap list group in the component, so apologies if you don't have this installed and it looks crap.
- I had considered combining the keydown events in the directive into a single event, however because I need to return a value from there I decided to keep them separate.
I'm fairly new to Angular2 so would of course appreciate your suggestions and feedback on any aspect of this implementation.
javascript html css angular-2+ autocomplete
add a comment |Â
up vote
0
down vote
favorite
I've tried to build a fairly simple, reusable and functional Angular2 autocomplete component.
The component:
import Component, Input, Output, OnInit, ContentChild, EventEmitter, HostListener from '@angular/core';
import Observable from "rxjs/Observable";
import AutoCompleteRefDirective from "./autocomplete.directive";
@Component(
selector: 'autocomplete',
templateUrl: './autocomplete.component.html',
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;
The component HTML:
<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>
The component CSS:
.autocomplete-wrapper
position: relative;
.autocomplete
position: absolute;
z-index: 100;
width: 100%;
The directive:
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:
The highlight pipe:
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>";
)
The implementation:
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));
A few thoughts:
- The use of both a Component and a Directive isn't ideal but I couldn't figure out a better way to do it.
- The
getDataproperty andsearch()functions have been separated so as to mimic a real life service call scenario. It took me an age working out how to wire this up using a property and=>! But is there a better way, especially when dealing with async calls? - The highlight pipe isn't necessary but it does improve the experience so I figured I'd include it here.
- I'm leveraging the bootstrap list group in the component, so apologies if you don't have this installed and it looks crap.
- I had considered combining the keydown events in the directive into a single event, however because I need to return a value from there I decided to keep them separate.
I'm fairly new to Angular2 so would of course appreciate your suggestions and feedback on any aspect of this implementation.
javascript html css angular-2+ autocomplete
add a comment |Â
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I've tried to build a fairly simple, reusable and functional Angular2 autocomplete component.
The component:
import Component, Input, Output, OnInit, ContentChild, EventEmitter, HostListener from '@angular/core';
import Observable from "rxjs/Observable";
import AutoCompleteRefDirective from "./autocomplete.directive";
@Component(
selector: 'autocomplete',
templateUrl: './autocomplete.component.html',
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;
The component HTML:
<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>
The component CSS:
.autocomplete-wrapper
position: relative;
.autocomplete
position: absolute;
z-index: 100;
width: 100%;
The directive:
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:
The highlight pipe:
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>";
)
The implementation:
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));
A few thoughts:
- The use of both a Component and a Directive isn't ideal but I couldn't figure out a better way to do it.
- The
getDataproperty andsearch()functions have been separated so as to mimic a real life service call scenario. It took me an age working out how to wire this up using a property and=>! But is there a better way, especially when dealing with async calls? - The highlight pipe isn't necessary but it does improve the experience so I figured I'd include it here.
- I'm leveraging the bootstrap list group in the component, so apologies if you don't have this installed and it looks crap.
- I had considered combining the keydown events in the directive into a single event, however because I need to return a value from there I decided to keep them separate.
I'm fairly new to Angular2 so would of course appreciate your suggestions and feedback on any aspect of this implementation.
javascript html css angular-2+ autocomplete
I've tried to build a fairly simple, reusable and functional Angular2 autocomplete component.
The component:
import Component, Input, Output, OnInit, ContentChild, EventEmitter, HostListener from '@angular/core';
import Observable from "rxjs/Observable";
import AutoCompleteRefDirective from "./autocomplete.directive";
@Component(
selector: 'autocomplete',
templateUrl: './autocomplete.component.html',
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;
The component HTML:
<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>
The component CSS:
.autocomplete-wrapper
position: relative;
.autocomplete
position: absolute;
z-index: 100;
width: 100%;
The directive:
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:
The highlight pipe:
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>";
)
The implementation:
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));
A few thoughts:
- The use of both a Component and a Directive isn't ideal but I couldn't figure out a better way to do it.
- The
getDataproperty andsearch()functions have been separated so as to mimic a real life service call scenario. It took me an age working out how to wire this up using a property and=>! But is there a better way, especially when dealing with async calls? - The highlight pipe isn't necessary but it does improve the experience so I figured I'd include it here.
- I'm leveraging the bootstrap list group in the component, so apologies if you don't have this installed and it looks crap.
- I had considered combining the keydown events in the directive into a single event, however because I need to return a value from there I decided to keep them separate.
I'm fairly new to Angular2 so would of course appreciate your suggestions and feedback on any aspect of this implementation.
javascript html css angular-2+ autocomplete
edited Jan 14 at 23:42
Jamalâ¦
30.1k11114225
30.1k11114225
asked Jan 8 at 10:56
James Law
200128
200128
add a comment |Â
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f184569%2fangular-2-autocomplete%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password