4

I'm using the new Angular local variable assignment feature to reference the result of an async pipe in child elements under an ngIf:

<nav *ngIf="dataService.count$ | async as count">
  <div *ngIf="count > 1">
    There's more than one!  It's actually {{count}}.
  </div>
</nav>

As you can see, I have an extra div in the hierarchy just to grab the result of the async pipe and then do a comparison against it. I feel like I should be able to do this in a single expression but I can't find enough documentation about the new feature. I've tried a variety of approaches with parens, using | async; let count instead, etc, but I can't figure out how to do both things in one line.

(A pointer to any documentation on local variable declaration syntax in ngIf would be much appreciated too -- I had never heard of foo$ | async as foo until I saw it here on SO.)

Coderer
  • 25,844
  • 28
  • 99
  • 154
  • 5
    You could use `ng-container`, this still gives you an extra 'container' in the code, but not when rendering the view. – Jan_V Aug 21 '17 at 10:57
  • This question should have angular 4 tag. – Sangram Nandkhile Aug 21 '17 at 11:21
  • My answer here is not really the answer to your question, but very related, and the same things can be applied: https://stackoverflow.com/a/44696109/2521893 – Fredrik Lundin Aug 21 '17 at 12:06
  • 1
    It's in Angular doc: https://angular.io/api/common/NgIf and the same kind of question asked here: github.com/angular/angular/issues/16173 – Vega Aug 21 '17 at 12:31
  • Thanks Vega, I did read those docs and while they mention `async as` they don't say anything about `foo$ | async; let foo` which I believe also works. Based on your GH issue link, it looks like this doesn't have a shorthand yet, and I might be stuck with 2 elements. – Coderer Aug 22 '17 at 09:13
  • Turns out I should have been using `ng-template` instead of a `div` but otherwise the solution in the OP is what I'm still using. There are a few issues open with quality of life improvements like this (or e.g. having an "empty" construct for `*ngFor`) but I haven't seen any movement on them in a long time. – Coderer Jul 19 '19 at 13:43
  • Per @Jan_V my last comment above is wrong. `ng-template` does not normally render but `ng-container` does. – Coderer Jul 01 '21 at 14:40
  • It's not bad to have extra divs or spans or containers. I know it's not the prettiest solution but sometimes I have to nest things another layer for these kinds of operations – Chad K Jul 01 '21 at 14:56
  • Changing the structure of your DOM to suit the foibles of the templating language is a hassle, though. For example, your CSS -- or worse, the CSS of a library you're relying on, like Angular Material -- may use direct-descendent selectors that break if you insert an unnecessary `div` or `span` just to make Ivy happy. It's just a good practice to avoid the excess structure if possible. – Coderer Jul 01 '21 at 16:02

1 Answers1

2

There's still not an easy solution to my original question but I have found a related pattern that might help others in a similar situation:

<div id="top-container" *ngIf="{
  count: dataService.count$ | async,
  otherVal: dataService.val$ | async
}; let props">
  <nav *ngIf="props.count > 1">
    There's more than one!  It's actually {{props.count}}.
  </nav>
  <span *ngIf="props.otherVal?.startsWith('abc')">...</span>
</div>

Basically you can define an inline plain object whose values are async-pipe subscriptions inside the *ngIf structural directive of a top level element. The object will always be defined (after ViewInit, which is important because it breaks @ViewChild(..., {static: true}!) so the if-condition never fails and the top level DOM element always exists. But thanks to the way the async-pipe works, any change to the Observables that back any property will trigger change detection in your component, and you only get one subscription to each.

If you don't have a top level component to stick your bag-of-async-pipes into, or the existence of a top level *ngIf overcomplicates your use of ViewChild, you can use nested ng-container as @Jan_V suggested in the comments, which avoids unused elements in the resulting DOM structure. (I'll leave the question open in case there's a better answer out there.)

Coderer
  • 25,844
  • 28
  • 99
  • 154