doe
doe

Reputation: 455

ngFor Observable with async pipe in ngIf displays no results

I've got an input with dropdown list that isn't displaying results. It in an ngIf with async. The dropdown list is displayed when I remove the ngIf code so I think it must be either the ngIf check or how I've setup the test observable service. When I press a key entering 1 value nothing is displayed, the second time I enter a key it displays the list.

<div class="format-options" *ngIf="(definitions | async)?.length > 0">
  <div *ngFor="let definition of definitions | async" class="search-result">
    <div>{{definition.name}}</div>
    <div>{{definition.description}}</div>
  </div>
</div>

Search Service:

searchTerm(term: string): Observable<Array<ObjectDefinition>>

    let observer = new Observable<Array<ObjectDefinition>>((subscription) => {

      const timer$ = interval(1000);

      timer$.subscribe(() => {
        console.log('timer 1 second');
        subscription.next(this.objectDefinitions);
        subscription.complete();
      });

    });

    return observer;
}

Component:

constructor(private definitionService: DefinitionService) {
    this.definitions = this.searchInput.valueChanges
                        .pipe(
                            tap(value => console.log('input')),
                            //startWith(''),
                            debounceTime(500),
                            //distinctUntilChanged(),
                            switchMap(value => this.definitionService.searchTerm(value))
                        );
  }

Upvotes: 2

Views: 1901

Answers (2)

Mehdi Benmoha
Mehdi Benmoha

Reputation: 3935

You can set the result of the async operation directly in the *ngIf directive like that:

<div class="format-options" *ngIf="definitions | async as defs">
  <div *ngFor="let definition of defs" class="search-result">
    <div>{{definition.name}}</div>
    <div>{{definition.description}}</div>
  </div>
</div>

Then no need for the second async pipe.

PS: Never subscribe in your services, pefere using RxJs operators.

Upvotes: 5

Poul Kruijt
Poul Kruijt

Reputation: 71891

I guess the first *ngIf subscribes through the async pipe. Once this returns a result, the observable completes, and the *ngFor tries to susbcribe to an already completed observable which does not replays it's last emit. You can fix this by adding a shareReplay(1) pipe:

this.definitions = this.searchInput.valueChanges.pipe(
  tap(value => console.log('input')),
  debounceTime(500),
  switchMap(value => this.definitionService.searchTerm(value)),
  shareReplay(1)
);

The whole searchTerm observable looks a bit weirdly constructed though, and you should have a look at how to improve that one, but it's a bit difficult to see what you want to achieve there. Are you trying to debounce it by 1000ms and only emit once?

To prevent the use of double subscriptions in your template, you can also change your template a bit:

<ng-container *ngIf="definitions | async as defs">
  <div class="format-options" *ngIf="defs.length > 0">
    <div *ngFor="let definition of defs" class="search-result">
      <div>{{definition.name}}</div>
      <div>{{definition.description}}</div>
    </div>
  </div>
</ng-container>

Upvotes: 5

Related Questions