typescriptasynchronousserviceobservableangular-ngselect

ng-select can't retrieve data from a service


I'm using ng-select for a form where the user can search for an activity to add to the form. Basically the ng-select component calls a function when someone types in the field, this function will call the service that will return an observable of the array containing the activities which name matches the input, then the ng-select component subscribes to the observable via the async.
The issue I'm facing is that it seems that the ng-select can't get the Observable or can't subscribe to it, I'm not sure. I only succeed to make it work when adding delay to the Observable returned by the service.

Html:

<label for="class">Activité</label>
   <ng-select [items]="activities$ | async"
              groupBy="type"
              [(ngModel)]="selectedActivity"
              [ngModelOptions]="{standalone: true}" 
              (search)="inputChanged($event)">
   </ng-select>

Typescript:

activities$!: Observable<Activity[]>;
selectedActivity!: Activity;

private modelChanged: Subject<string> = new Subject<string>();
private searchSubscription!: Subscription;
debounceTime = 500;

constructor(private dataService: DataService) { }

ngOnInit(): void {
  this.initActivitySearchListener();
};

initActivitySearchListener(): void {
   this.searchSubscription = this.modelChanged
      .pipe(
        debounceTime(this.debounceTime),
      )
      .subscribe(input => {
        console.log(input)
        this.updateSearch(input);
   });
}

//When typing in activity searchbar
inputChanged($event: any) {
   this.modelChanged.next($event.term);
}

updateSearch(term: string) {
   console.log(term);
   this.activities$ = this.dataService.getActivitiesForList(term);
   this.dataService.getActivitiesForList(term).subscribe(data => console.log(data));
}

Service:

getActivitiesForList(inputString: string): Observable<Activity[]> {

    let parsedResult: Activity[] = [];

    this.http.get<any>(this.HOST + this.API_URI + 'activity?searchText=' + inputString).subscribe((data) => {
      for (const activity of data) {
        switch (activity.type) {
          case "dungeon":
            let tempBossArray: any[] = [];
            activity.dungeon_boss.forEach((bossObject: any) => {
              tempBossArray.push(bossObject.name);
            });
            parsedResult.push({ id: activity.id, name: activity.name + ' (' + tempBossArray.toString() + ') ' + '[Niv. ' + activity.level + ']', type: "Donjon" });
            break;

          case "quest":
            parsedResult.push({ id: activity.id, name: activity.name + ' (' + activity.category + ')', type: "Quête" });
            break;

          default:
            break;
        }
      }
    });
    return of(parsedResult).pipe(delay(100));
  };

On that last line you see the pipe(delay(100)), if I get rid of it, I won't get any data in my ng-select even tho I do get the array when checking via console.log()


Solution

  • I found the solution. My intuition was correct, the issue was the manual of() not acting like a natural Observable in the stream, so because of the delay generated by the process on the data in the service, the front was getting an empty array. To fix this, you need to process all data in the pipe() using map() like so:

    getActivitiesForList(inputString: string): Observable<Activity[]> {
        return this.http.get<any>(this.HOST + this.API_URI + 'activity?searchText=' + inputString).pipe(map((data) => {
          let parsedResult: Activity[] = [];
          for (const activity of data) {
            switch (activity.type) {
              case "dungeon":
                let tempBossArray: any[] = [];
                activity.dungeon_boss.forEach((bossObject: any) => {
                  tempBossArray.push(bossObject.name);
                });
                parsedResult.push({
                  id: activity.id, 
                  name: activity.name + ' (' + tempBossArray.toString() + ') ' + '[Niv. ' + activity.level + ']', 
                  type: 'dungeon',
                  pretty_type: 'Donjon'
                });
                break;
    
              case "quest":
                parsedResult.push({
                  id: activity.id, 
                  name: activity.name + ' (' + activity.category + ')', 
                  type: 'quest',
                  pretty_type: 'Quête'
                });
                break;
    
              default:
                break;
            }
          }
          return parsedResult;
        }));
      }