Using ng-bootstrap, I have implemented ngbTypeahead which makes AJAX calls in my codebase. If I press Tab in the textbox before the results have returned, the AJAX call is cancelled, and no results are displayed.
While it's fine that no results are displayed, I'm actually looking to auto-populate the first result in the background (similar to what would happen if the results are displayed).
You might see this scenario in a data input team where the users know the code of what they're looking for but the system is too slow for them. If they don't know, they could type something in and wait for suggestions from the application. Or, they can enter the code (or even full name) and skip onto the next field and the application will fill it in in the background.
I have a list of animals in my database with a couple of similar examples here:
[
{ "Code": "CANI", "Name": "Canis familiaris" },
{ "Code": "CANIL", "Name": "Canis lupus" }
]
These results are usually ordered by most common first.
So, for example, if I search "Cani" and my first result is "Canine familiaris", then I would hope that Canine familiaris is automatically selected. However, if I press Tab what I end up with is just "Cani" which is incorrect because it's not the selected result.
I have two basic scenarios:
- If there was one result, select that.
- If there are 2+ results AND one of the results matches the Code or Name of one of the results, select that. NB: This rule should be hit by my search for Cani as this is the same as the Code).
- If there are no results or none of the codes in the results match, wipe the text field.
Here is a sample of my code:
Typescript:
AnimalSource = (text$: Observable<string>) => text$.pipe(
debounceTime(200),
distinctUntilChanged(),
switchMap(searchText => {
if (searchText === '') return [];
return this.SearchAnimals(searchText);
})
);
// Added for completeness. In our application, the HttpClient is abstracted away into a separate service but this is essentially what it's doing.
SearchAnimals(query: string): Observable<Animal[]> {
const formParams: FormData = new FormData();
formParams.append('Query', query);
return this.httpClient.post(AnimalSearchUrl, formParams)
.pipe(map(data: IAnimalSearchResponse) => {
if (data && data.Results?.length) return data.Results;
// data.Results would contain something like the array mentioned above.
return [];
});
}
HTML:
<input
type="text"
autocomplete="off"
class="form-control"
container="body"
name="Animal"
(selectItem)="ChooseAnimal($event)"
[(ngModel)]="Criteria.Animal"
[ngbTypeahead]="AnimalSource"
[resultTemplate]="AnimalSearchListTemplate" />
<ng-template #AnimalSearchListTemplate let-result="result">
<p class="mb-1">{{ result.Code }} / {{ result.Name }}</p>
</ng-template>
I believe my problem is the use of switchMap but I don't know what can be done to alter its behaviour in order to prevent it cancelling. And now I've been staring at the problem for so long I'm beginning to question it entirely. Most of my searches have been relating to ngbTypeahead but I now think that it's my use (or misuse) of RxJs Observables that's the root cause.
I assume there's a different mechanism which can be used to store the results, regardless of whether the AJAX call is cancelled, but I can't find one.
Any help understanding how I can achieve the two scenarios above would be greatly appreciated.