When clicking on the input field, I want the typeahead feature to display the first 5 results. I have created a solution based on the ngbTypeahead documentation.
app.component.html
<div class="form-group g-0 mb-3">
<input id="typeahead-prevent-manual-entry" type="text" class="form-control"
placeholder="Big dataset"
formControlName="bigDataset"
[ngbTypeahead]="search"
[inputFormatter]="valueFormatter"
[resultFormatter]="valueFormatter"
[editable]="false"
[focusFirst]="false"
(focus)="stateFocus$.next($any($event).target.value)"
(click)="stateClick$.next($any($event).target.value)"
#instance="ngbTypeahead" />
</div>
app.component.ts
type BigDataset: {
id: string,
name: string
}
export class AppComponent implements OnInit {
dataset: BigDataset[];
@ViewChild('instance', {static: true}) instance: NgbTypeahead;
focus$ = new Subject<string>();
click$ = new Subject<string>();
constructor(
private formBuilder: FormBuilder,
) { }
ngOnInit(): void {
this.dataForm = this.formBuilder.group({
bigDataset: ["", [Validators.required]],
});
}
getBigDataset() {
//Excluded for simplicity. This returns a set of objects (~3000)
//of type BigDataset and assigns it to this.dataset.
}
valueFormatter = (value: any) => value.name;
search: OperatorFunction<string, readonly BigDataset[]> = (text$: Observable<string>) => {
const debouncedText$ = text$.pipe(debounceTime(100), distinctUntilChanged());
const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
const inputFocus$ = this.focus$;
return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(map(term => (term === '' ? this.dataset
: this.dataset.filter(data => data.name.toLowerCase().indexOf(term.toLowerCase()) > -1)).slice(0, 5))
);
};
}
Although the code works as intended, there is an issue when clicking on the input field immediately after page initialization, resulting in the following error:
ERROR TypeError: Cannot read properties of undefined (reading 'slice')
at org-address.component.ts:93:109
// The rest of the error messages follow...
The root cause seems to be that the typeahead attempts to display data before completing the map function. Since the mapping process duration can vary with dataset size, I am seeking a way to disable or implement an alternative until the mapping completes without having to wait indefinitely.
I attempted to programmatically disable and enable the form field using finalize(), but encountered difficulty as the field remained disabled even after the completion of mapping.
search: OperatorFunction<string, readonly BigDataset[]> = (text$: Observable<string>) => {
this.dataForm.get('bigDataset')?.disable();
// The remaining functionality and code snippets go here...
return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
finalize(() => this.dataForm.get('bigDataset')?.enable()),
map(term => (term === '' ? this.dataset
: this.dataset.filter(data => data.name.toLowerCase().indexOf(term.toLowerCase()) > -1)).slice(0, 5))
);
};
If anyone can provide assistance or suggestions regarding this matter, it would be greatly appreciated.