I can't get the results from my songs service to show in my ng-bootstrap typeahead results. I am getting the error: "Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays."
I have tried following the wikipedia example on the ng-bootstrap site: https://ng-bootstrap.github.io/#/components/typeahead/examples#http
My .ts file has:
formatter = (x: { title: string }) => x.title;
searchSongTitles = (text$: Observable<string>) =>
text$.pipe(
debounceTime(800),
distinctUntilChanged(),
tap(() => this.loadingNavbarSearchSongs$ = true),
switchMap(term =>
this.songsService.getSongsQuickSearch(term, this.titleMatchType$, this.sort$, this.sortDirection$).pipe(
tap(() => this.loadingNavbarSearchSongsFailed$ = false),
catchError(() => {
this.loadingNavbarSearchSongsFailed$ = true;
return of([]);
})
)
),
tap(() => this.loadingNavbarSearchSongs$ = false)
)
and my .html file has:
<input id="songtitles-search" placeholder="Song Title" type="text"
class="form-control form-control-sm my-sm-0 mr-sm-2"
name="song_titles_search" [(ngModel)]="songTitlesSearchText$"
[ngbTypeahead]="searchSongTitles" [resultFormatter]="formatter"
[inputFormatter]="formatter">
The model:
songsService.getSongsQuickSearch returns an
Observable<Song[]>
where Song has these properties:
id: number;
title: string;
I need to take the array of songs coming from the getSongsQuickSearch method so they are clickable song title links in the dropdown that link to the song details page based on its id. For example:
<a [routerLink]="['/song-details', song.id]">{{ song.title }}</a>
I ended up using ng-bootstrap typeahead with a custom response view. This is using Angular "^7.0.1" with "bootstrap": "^4.1.3" and @ng-bootstrap/ng-bootstrap": "^3.3.1"
Here is the custom CSS I used to make the search results show under the search input:
#search-results {
position: absolute;
width: 160px; /*How wide the results are*/
background: white;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
max-height: 500px; /* How far down the results will show */
overflow-y: auto; /* Show scrollbar if results overflow vertically */
overflow-x: none; /* Do not show scrollbar horizontally if results overflow */
border: 1px solid gray;
left: 150px; /*Where from the left side of the screen the results container starts*/
right: 0;
top: 44px; /*Make the results container show under search input*/
}
Here is the search view:
<form class="form-inline">
<input id="songtitles-search" placeholder="Song Title" type="text" class="navbar-
search form-control form-control-sm my-sm-0 mr-sm-2" name="song_titles_search"
[(ngModel)]="songTitlesSearchText$" [ngbTypeahead]="searchSongTitles">
<ul id="search-results" class="list-group">
<li *ngIf="loadingNavbarSearchSongs$" class="list-group-item">
<img class="small-image" style="width:8em; height:8em;"
src="assets/img/loading.gif" />
</li>
<li *ngFor="let song of songs$" class="list-group-item">
<a (click)="songSearchResultChosen()" [routerLink]="['/song-details',
song.id]">{{ song.title }}</a> - {{song.main_main_artist}}
</li>
<li *ngIf="showNoSearchResultsMessage$">
<strong>No Song(s) Found Matching Search Criteria</strong>
</li>
</ul>
<button class="btn btn-primary btn-sm my-2 my-sm-0 mr-sm-2" type="submit">
Search
</button>
</form>
Here is the typescript typeahead code:
searchSongTitles = (text$: Observable<string>) =>
text$.pipe(
debounceTime(800),
distinctUntilChanged(),
map(term => {
this.songs$ = [];
this.showNoSearchResultsMessage$ = false;
if (term.length > 0) {
this.songTitlesSearchText$ = term;
this.getSongs();
} else {
this.loadingNavbarSearchSongs$ = false;
}
}
)
)
Here is the getSongs method which calls a service I have setup
getSongs() {
this.loadingNavbarSearchSongs$ = true;
this.songsService.getSongsQuickSearch(this.songTitlesSearchText$, this.titleMatchType$,
this.sort$, this.sortDirection$).subscribe(
response => {
this.songs$ = response.songs;
this.loadingNavbarSearchSongs$ = false;
if (this.songs$.length <= 0) {
this.showNoSearchResultsMessage$ = true;
}
}
);
}
You can see a working example in the navbar at my website abovetempo.com Hope this helps someone else who is looking for this solution!