How to use Observable with Async pipe inside Angular2 click function

Hugh Hou picture Hugh Hou · Oct 4, 2016 · Viewed 9.9k times · Source

Situation: I am using FirebaseObjectObservable to populate my Ionic 2 (rc0) template. Template code:

 <p>{{(course | async)?.description}}</p>
 <button ion-button dark full large (click)="openDeckOnBrowser(course.deckUrl)">
  <ion-icon name='cloud-download'></ion-icon>
  <div>View Deck</div>
 </button>

The TS file is

  this.course = this.af.database.object('/bbwLocations/courses/' + courseId); 

this.course is a Firebase Object Observable. The problem is, this part won't work: (click)="openDeckOnBrowser(course.deckUrl). As the course.deckUrl is empty. I can not pass the value to the function.

Tho only hacky way I found to work around so far is this:

 <button id="{{(course | async)?.deckUrl}}" ion-button dark full large (click)="openDeckOnBrowser($event)">
  <ion-icon name='cloud-download'></ion-icon>
  <div id="{{(course | async)?.deckUrl}}">View Deck</div>
</button>

And on the click event:

  openDeckOnBrowser(event):void {
    console.log(event);
    let target = event.target || event.srcElement || event.currentTarget;
    let idAttr = target.attributes.id;
    let url = idAttr.nodeValue;
    console.log (url);
   }

But any official and easier way to approach this?

Answer

Fabian Keller picture Fabian Keller · Oct 4, 2016

In your template (click)="openDeckOnBrowser(course.deckUrl)" is evaluated as soon as the template is parsed. You do not use the async pipe here, hence the deckUrl is empty. You can fix this by adding a second async pipe:

<p>{{(course|async)?.description}}</p>
<button ... (click)="openDeckOnBrowser((course|async).deckUrl)">...</button>

However, this is not nice as two subscriptions will be created.

A (better) alternative:

The official docs on the AsyncPipe always use *ngFor to output a list of items and not *ngIf to output a single item. The reason is simple: the *ngIf directive does not allow any assignments. But we can work around this limitation:

The template looks as follows:

<div *ngFor="let course of course$|async">
    <p>Course: {{course?.name}}</p>
    <p>Url: {{course?.url}}</p>
</div>

And in the component, we'll just map the course to a list of a single course:

this.getCourse().map(c=>[c]);

See working Demo in Plunker