34

I have an Angular component that gets a service CatalogServiceinjected:

export class CatalogListComponent implements OnInit {
  catalog$: Observable<MovieResponseItem[]>;
  constructor(private catalogService: CatalogService) {}
  ngOnInit() {
    this.catalog$ = this.catalogService.userCatalog;
  }
}

This service returns an Observable<MovieResponseItem[]> on property userCatalog:

@Injectable()
export class CatalogService {
  get userCatalog(): Observable<MovieResponseItem[]> {
    return this._userCatalogSubject.asObservable();
  }
}

The MovieResponseItemis just a simple interface:

export interface MovieResponseItem {
  title: string;
}

Now I want to iterate the items and display a loading animation while the catalog queries the underlying service for data (that takes some time) - this works. This is the template used:

<div *ngIf="(catalog$ | async)?.length > 0; else loading">
   <ng-container *ngFor="let item of catalog$ | async">
     <div>{{item.title}}</div>
   <ng-container>
</div>
<ng-template #loading>loading animation...</ng-template>

This obviously displays the #loading template while the async is awaiting data. If the observable returns data, it iterates over the catalog values.

But now I want to separate this into this behaviour:

  • while we await data, display the loading animation
  • if we have a response from the service and the returned list is empty, show an information text (like "your catalog is empty") and do not iterate (as there is no data)
  • if we have a response from the service and the returned list has values, iterate the items (as in current state)

How can I achieve this?

1
  • You can add another <ng-container *ngIf="catalogService.userCatalog.length == 0"> <div>your catalogue response is empty</div> <ng-container> just below your ngFor container. Commented Sep 26, 2017 at 19:46

2 Answers 2

72
 <div *ngIf="catalog$ | async as catalog; else loading">
  <ng-container *ngIf="catalog.length; else noItems">
    <div *ngFor="let item of catalog">{{item.title}}</div>
  </ng-container>
  <ng-template #noItems>No Items!</ng-template>
 </div>
 <ng-template #loading>loading animation...</ng-template>

This should do the trick. Better to use as few async pipes as possible and just declare it "as" a template variable you can use where ever. Otherwise the stream will be executed once per async pipe which is a bad practice and could create unneeded http calls if this is http backed.

*edit for the syntax error

Sign up to request clarification or add additional context in comments.

17 Comments

Thanks for your answer! Unfortunately This never displays the loading template but always the noItem while waiting for the Observable answer.
the structure and scope needs to be just like i laid it out or it won't work. loading needs to be outside the async on ngIf and the noItems needs to be inside the async scope but outside the catalog.length scope
also I'm not sure what the source subject of your catalog$ observable is, but if it's a behavior subject, then this will never be "loading" as the value is always there immediately. if this is the case then you need to initialize it to null rather than [] as [] is "truthy" while null is falsey
Understood, probably my observable is the problem. I use, as you stated, a BehaviorSubject<MovieResponseItem[]> that is used with .asObservable(). I'll try to refactor this.
you can still initialize it to null as every value in typescript is nullable by default. You can also add .filter(v => !!v) to only pass through non null values which gives you the timing benefits of both subject types
|
1

hmmm.. https://github.com/angular/angular/issues/14479

Just throw in another ngIf - else condition.

<div *ngIf="(catalog$ | async);  else loading;">
   <div *ngIf="catalog$.length == 0; else empty;">
      <ng-container *ngFor="let item of catalog$ | async">
         <div>{{item.title}}</div>
      <ng-container>
   </div>
   <ng-template #empty>empty animation...</ng-template>
</div>
<ng-template #loading>loading animation...</ng-template>

2 Comments

catalog$ is an observable of a list so it will never have a length
Thank you, as seen on the other answer my catalog$ is never null (currently, will adjust it) thus the laoding animation is never displayed. Good linked issue!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.