Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(typeahead): add typing for typeahead source data #5647

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/src/ng-api-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3869,7 +3869,7 @@ export const ngdoc: any = {
},
{
"name": "typeahead",
"type": "any",
"type": "Typeahead",
"description": "<p>options source, can be Array of strings, objects or\nan Observable for external matching process</p>\n"
},
{
Expand Down
62 changes: 26 additions & 36 deletions src/typeahead/typeahead.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from '@angular/core';
import { NgControl } from '@angular/forms';

import { from, Subscription, isObservable } from 'rxjs';
import { from, Subscription, isObservable, Observable } from 'rxjs';
import { ComponentLoader, ComponentLoaderFactory } from 'ngx-bootstrap/component-loader';
import { debounceTime, filter, mergeMap, switchMap, toArray } from 'rxjs/operators';

Expand All @@ -25,13 +25,15 @@ import { TypeaheadConfig } from './typeahead.config';
import { getValueFromObject, latinize, tokenize } from './typeahead-utils';
import { TypeaheadOrder } from './typeahead-order.class';

type TypeaheadOption = string | {[key in string | number]: any};
type Typeahead = TypeaheadOption[] | Observable<TypeaheadOption[]>;

@Directive({selector: '[typeahead]', exportAs: 'bs-typeahead'})
export class TypeaheadDirective implements OnInit, OnDestroy {
/** options source, can be Array of strings, objects or
* an Observable for external matching process
*/
// tslint:disable-next-line:no-any
@Input() typeahead: any;
@Input() typeahead: Typeahead;
/** minimal no of characters that needs to be entered before
* typeahead kicks-in. When set to 0, typeahead shows on focus with full
* list of options (limited as normal by typeaheadOptionsLimit)
Expand Down Expand Up @@ -115,8 +117,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
/** fired when option was selected, return object with data of this option */
@Output() typeaheadOnSelect = new EventEmitter<TypeaheadMatch>();
/** fired when blur event occurs. returns the active item */
// tslint:disable-next-line:no-any
@Output() typeaheadOnBlur = new EventEmitter<any>();
@Output() typeaheadOnBlur = new EventEmitter<TypeaheadMatch>();

/**
* A selector specifying the element the typeahead should be appended to.
Expand All @@ -142,15 +143,13 @@ export class TypeaheadDirective implements OnInit, OnDestroy {

_container: TypeaheadContainerComponent;
isActiveItemChanged = false;
isTypeaheadOptionsListActive = false;
isFocused = false;
cancelRequestOnFocusLost = false;

// tslint:disable-next-line:no-any
protected keyUpEventEmitter: EventEmitter<any> = new EventEmitter();
protected keyUpEventEmitter: EventEmitter<string> = new EventEmitter();
protected _matches: TypeaheadMatch[];
protected placement = 'bottom-left';
// protected popup:ComponentRef<TypeaheadContainerComponent>;

private _typeahead: ComponentLoader<TypeaheadContainerComponent>;
private _subscriptions: Subscription[] = [];
Expand Down Expand Up @@ -195,10 +194,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
this.typeaheadWaitMs = this.typeaheadWaitMs || 0;

// async should be false in case of array
if (
this.typeaheadAsync === undefined &&
!(isObservable(this.typeahead))
) {
if (this.typeaheadAsync === undefined && !(isObservable(this.typeahead))) {
this.typeaheadAsync = false;
}

Expand Down Expand Up @@ -399,10 +395,10 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
this._subscriptions.push(
this.keyUpEventEmitter
.pipe(
debounceTime(this.typeaheadWaitMs),
debounceTime<string>(this.typeaheadWaitMs),
switchMap(() => this.typeahead)
)
.subscribe((matches: any[]) => {
.subscribe((matches: TypeaheadOption[]) => {
this.finalizeAsyncCall(matches);
})
);
Expand All @@ -412,27 +408,26 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
this._subscriptions.push(
this.keyUpEventEmitter
.pipe(
debounceTime(this.typeaheadWaitMs),
debounceTime<string>(this.typeaheadWaitMs),
mergeMap((value: string) => {
const normalizedQuery = this.normalizeQuery(value);

return from(this.typeahead)
.pipe(
filter((option: any) => {
filter((option: TypeaheadOption) => {
return option && this.testMatch(this.normalizeOption(option), normalizedQuery);
}),
toArray()
);
})
)
.subscribe((matches: any[]) => {
.subscribe((matches: TypeaheadOption[]) => {
this.finalizeAsyncCall(matches);
})
);
}

// tslint:disable-next-line:no-any
protected normalizeOption(option: any): any {
protected normalizeOption(option: TypeaheadOption): string {
const optionValue: string = getValueFromObject(
option,
this.typeaheadOptionField
Expand All @@ -445,8 +440,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
}

protected normalizeQuery(value: string): string | string[] {
// If singleWords, break model here to not be doing extra work on each
// iteration
// If singleWords, break model here to not be doing extra work on each iteration
let normalizedQuery: string | string[] = (this.typeaheadLatinize
? latinize(value)
: value)
Expand Down Expand Up @@ -480,7 +474,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
return match.indexOf(test) >= 0;
}

protected finalizeAsyncCall(matches: any[]): void {
protected finalizeAsyncCall(matches: TypeaheadOption[]): void {
this.prepareMatches(matches || []);

this.typeaheadLoading.emit(false);
Expand Down Expand Up @@ -518,7 +512,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
}
}

protected prepareMatches(options: any[]): void {
protected prepareMatches(options: TypeaheadOption[]): void {
const limited = options.slice(0, this.typeaheadOptionsLimit);
const sorted = !this.typeaheadOrderBy ? limited : this.orderMatches(limited);

Expand All @@ -539,18 +533,14 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
// add each item of group to array of matches
matches = matches.concat(
sorted
.filter(
// tslint:disable-next-line:no-any
(option: any) =>
getValueFromObject(option, this.typeaheadGroupField) === group
.filter((option: TypeaheadOption) =>
getValueFromObject(option, this.typeaheadGroupField) === group
)
.map(
// tslint:disable-next-line:no-any
(option: any) =>
new TypeaheadMatch(
option,
getValueFromObject(option, this.typeaheadOptionField)
)
.map((option: TypeaheadOption) =>
new TypeaheadMatch(
option,
getValueFromObject(option, this.typeaheadOptionField)
)
)
);
});
Expand All @@ -568,7 +558,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
}
}

protected orderMatches<T>(options: T[]): T[] {
protected orderMatches(options: TypeaheadOption[]): TypeaheadOption[] {
if (!options.length) {
return options;
}
Expand Down Expand Up @@ -603,7 +593,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy {
return options;
}

return options.sort((a: T, b: T) => {
return options.sort((a: TypeaheadOption, b: TypeaheadOption) => {
const stringA = getValueFromObject(a, field);
const stringB = getValueFromObject(b, field);

Expand Down