52

my template have something like...

<div> {{ (profile$ | async).username}}</div>
<div> {{ (profile$ | async).email}}</div>

Is there a way to assign (profile | async) to a local variable? having to type it out for every single field is bad for readability.

Thanks.

5 Answers 5

76

Since Angular 4.0.0-rc.1 (2017-02-24), you can use the enhanced *ngIf syntax. This let's you assign the result of an *ngIf condition in a template local variable:

<div *ngIf="profile$ | async as profile">
    <div>{{profile.username}}</div>
    <div>{{profile.email}}</div>
</div>

The updated *ngIf documentation goes into further detail and gives many good examples using async and *ngIf together.

Be sure to also check out the then and else statements of *ngIf.

Cory Rolan has made a short tutorial that you can digest in 5–10 minutes.

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

2 Comments

@Hinrich no, it doesn't matter actually. async pipes are memoized and internally the same subscribtion gets used. the only benefit is the readability
@Phil In fact, it is not like this, it is better to use a variable because if you use the pipe several times it will create a different subscriptions for each one, which maybe it is not what you want. You could check it easily logging subscription.
35

The best approach would be to create a new component, e.g. in this case app-profile and to pass profile | async to that component. Inside the component you can name the variable whatever you like.

<app-profile [profile]="profile | async"></app-profile>

Another way of doing it would be to use *ngIf with the 'as' syntax

<ng-container *ngIf="profile | async as p">
   <div> {{ p.username }}</div>
   <div> {{ p.email }}</div>
</ng-container>

4 Comments

Yes, I have seen a few a few example doing this, which is probably better for re-usability. Thanks!
If you're using ChangeDetectionStrategy.OnPush, this doesn't really help, since you'd end up using an observable in the app-profile component to respond to changes in the profile @Input
@chrismarx no, in this example the app-profile input variable profile would not be an observable. Each value that passes through outer, observable profile would be piped through async, which is going to push a single value into the input. That will trigget the OnPush change detection, which will cause app-profile to render the new value.
@JasonJackson hmm, I think you're right, I forgot that onpush updates both on changes to something async as well as "@Inputs"
4

This is a bit hacky but I've seem a lot of examples using something like this:

<template ngFor let-profile [ngForOf]="profile$ | async">
  <div> {{ profile.username }}</div>
  <div> {{ profile.avatar }}</div>
  <div> {{ profile.email }}</div>
  ...
</template>

4 Comments

How is this different from <div *ngFor="let profile of profile$ | async">?
@torazaburo With the one you mentioned you're creating a <div> wrapper on every iteration. The <template> element doesn't generate any output and is just used to define a block in your layout, however <template> doesn't support the short syntax (*ngFor) only the long one.
Can I use template to simply assign a variable to the latest value of an observable, without it being an array that I want to loop over?
@torazaburo Maybe, but I don't see any reason to do that in your template instead of on its own controller. You can still use an observable in your template and add another observer to it from your controller to run whatever piece of code when its value changes. You'll have more power by making this variable assignment you mentioned from within your controller. Hope that made any sense to you.
4

There is an alternative but it is more tedious. The solution that Günter Zöchbauer propsed mitigates that fact.

The alternative would be to subscribe to the observable inside of the component and then use the subscription variable in your template. You must unsubscribe the subscription in the OnDestroy event to prevent memory leaks. The async pipe do that for you. That is why the accepted solution is cleaner. But if you dont want to create a new component this is the way to go.

Comments

1

If you want access to object on inner vm$ like this type vm$: Observable<{ profile: {} }>;

try below snippet and don't forget ? mark.

<ng-container *ngIf="(vm$ | async)?.profile as profile">
  <div> {{ profile.username }}</div>
  <div> {{ profile.email }}</div>
</ng-container>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.