1

I am trying to do the following:

<div *ngIf="(observable$ | async) as results?.length > 0"> <-- here is the error
  <span *ngFor="let result of results>{{result}}</span>"

i.e. only draw this <div> when the results are available AND there are more than 1 result

The first line <div> is causing error. A two <div>s solution exists:

<div *ngIf="(observable$ | async) as results">
  <div *ngIf="results?.length > 0">
    <span *ngFor="let result of results>{{result}}</span>"

Can it be done in a single <div> without adding another container?

user1589188
  • 4,159
  • 12
  • 49
  • 99
  • Not sure, but try `((observable$ | async) as results)?.length > 0` – Elias Soares Feb 11 '20 at 00:57
  • @EliasSoares thanks but it does not work. – user1589188 Feb 11 '20 at 01:03
  • 1
    I would defer this amount of logic to the controller and keep the template clean. – Phix Feb 11 '20 at 02:13
  • Does this answer your question? [Angular \*ngIf variable with async pipe multiple conditions](https://stackoverflow.com/questions/49296784/angular-ngif-variable-with-async-pipe-multiple-conditions) – racraman Feb 11 '20 at 02:16
  • @Phix I beg to differ. The template should handle what to display, the controller just feed the data. – user1589188 Feb 11 '20 at 02:24
  • @racraman the solution there is to use multiple async pipe, as if they do not use `as`. Mine here specifically ask about using `as`, I guess this is the difference. – user1589188 Feb 11 '20 at 02:26
  • @user1589188, right, feed the template clean data. A few lines of rxjs would circumvent the visual clutter. – Phix Feb 11 '20 at 02:28

3 Answers3

1

? can't be used with as in *ngIf statements. The best you can do is (observable$ | async)?.length > 0.

Also, when you don't need two nested divs rendered in HTML (or any for that matter, but you still need *ngIf / *ngFor), you can use an <ng-container> which does not render as an HTML element:

<ng-container *ngIf="(observable$ | async) as results">
  <div *ngIf="results?.length > 0">
    <span *ngFor="let result of results>{{result}}</span>

If you are trying to check if an element is not empty before you iterate over it, you don't really need to explicitly check for that, because *ngFor will gracefully handle empty arrays/lists. So you should be able to simplify your template to this:

<ng-container *ngIf="(observable$ | async) as results">
  <span *ngFor="let result of results>{{result}}</span>

or even further to this:

<div *ngFor="let item of observable$ | async">{{result}}</div>
ulmas
  • 1,787
  • 10
  • 19
  • Thanks for many alternatives, they should be useful for others. So back to my original question, do you believe there is no way to use ? after `as`? – user1589188 Feb 11 '20 at 04:32
  • 1
    You can't use ? with as. The best you can do is `(observable$ | async)?.length > 0` – ulmas Feb 11 '20 at 15:21
  • OK, please add this in your answer so I can pick yours as the answer, because this is what I asked and then you can suggest alternatives like above. – user1589188 Feb 12 '20 at 00:20
0

In this scenario, I like using two divs because it feels cleaner and more readable to me. That's different for different people, though, so it's really your preference.

<div *ngIf="observable$ | async as results">
  <div *ngIf="results && results.length > 0">
    <span *ngFor="let result of results>{{result}}</span>"
  </div>
</div>
Kyler Johnson
  • 1,466
  • 12
  • 13
0

You can't have *ngFor and *ngIf on the same element, so you can use ng-container to prevent the additional div wrapper being included in the output:

<ng-container *ngIf="(observable$ | async).length; else noContent">
  <div *ngFor="let result of (observable$ | async)">
    <h3>{{ result.name }}</h3>
    <p>{{ result.price | currency }}</p>
  </div>
</ng-container>

<!-- optional -->
<ng-template #noContent>
    Nothing to show.
</ng-template>
import { Component, OnInit } from "@angular/core";
import { of, Observable } from "rxjs";
import { map, filter, tap } from "rxjs/operators";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
  observable$: Observable<{name: string, price: number}[]>;

  ngOnInit() {
    // To simulate requests with some/no results 
    // Change to 1 to always return data, 0 for empty results.
    const chance = Math.random();
    this.observable$ = this.getMockData(chance).pipe(
      map(res => res.results)
    );
  }

  getMockData(random) {
    const results =
      random <= 0.5
        ? { results: [] }
        : {
            results: [
              { name: "Widget", price: 300 },
              { name: "Widget 2", price: 50 },
              { name: "Widget 3", price: 75 }
            ]
          };
    return of(results);
  }
}

Output:

<div _ngcontent-sqx-c0="">
  <h3 _ngcontent-sqx-c0="">Widget</h3>
  <p _ngcontent-sqx-c0="">$300.00</p>
</div>
<div _ngcontent-sqx-c0="">
  <h3 _ngcontent-sqx-c0="">Widget 2</h3>
  <p _ngcontent-sqx-c0="">$50.00</p>
</div>
<div _ngcontent-sqx-c0="">
  <h3 _ngcontent-sqx-c0="">Widget 3</h3>
  <p _ngcontent-sqx-c0="">$75.00</p>
</div>

Stackblitz

Phix
  • 7,757
  • 3
  • 31
  • 57
  • Thank you. But sorry if my question caused some confusion here. I wasn't asking about combining `ngIf` and `ngFor` together in one container, my question title said to use ? after `as`. The line that is in question is `*ngIf="(observable$ | async) as results?.length > 0"`, which causes error. The `` I added is just to complete the code segment, it has nothing to do with my question. – user1589188 Feb 11 '20 at 03:30