How to use Observable with Async pipe inside Angular2 click function

12,013

Solution 1

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

Solution 2

Just subscribe to the observable in your code, and store the course itself in the component:

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

Then in you view:

<p>{{ course?.description }}</p>
<button ion-button dark full large (click)="openDeckOnBrowser(course?.deckUrl)">

(although I would probably use ng-if and only show the description and the button once the course is available)

Share:
12,013
Hugh Hou
Author by

Hugh Hou

Backbone Newbie!

Updated on June 15, 2022

Comments

  • Hugh Hou
    Hugh Hou almost 2 years

    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?

  • Hugh Hou
    Hugh Hou over 7 years
    Well subscribe to the object defeat the whole point of Firebase Observable project. Using async pipe to optimize the code as it subscribe and unsubscribe when it is done. So it simplify lots of codes for me. With your solution, I will need to unsubscribe when the view destroy and all other cleaning jobs that aysnc pipe could dont for me. I looking for a solution that I don't even need to manually subscribe to the firebase object.
  • Fabian Keller
    Fabian Keller over 7 years
    I just noticed that my latest changes to the Plunker were not saved yesterday. I fixed it and now it should be all correct.
  • Fabian Keller
    Fabian Keller over 7 years
    The Plunker referenced in the answer works fine with the concepts presented in the answer. I would be glad to help you if you could post your non-working solution in a Plunker, as otherwise I cannot judge where the fault might be located.
  • Jeff
    Jeff almost 7 years
    I used this... and if the item isn't an iterable... you can just wrap it in a []
  • rafaelbiten
    rafaelbiten over 6 years
    This is super nice! The only thing I did to make the whole thing act more like a *ngIf was to map it this way instead: .map(i => i ? [i] : undefined); On my case, this is an Observable<string> of an url used in a load more button. If the Observable gets nothing, means we should hide the button. This did the trick.
  • Kevin Beal
    Kevin Beal over 5 years
    You couldn't use the async pipe inside of an action even if you wanted to. It's not allowed.
  • Wildhammer
    Wildhammer almost 5 years
    action expressions cannot contain pipes