How to pass a value inside an observable to (click) handler in Angular2
Asked Answered
M

3

4

I try to learn observable programming with Angular 2 BUT I don't get one important point - how to pass in the value that is in an observable in the (click) event in the template level (without subscribing the observable in the TS file and turn the observable value into member variable).

As lots of Firebase experts suggest this "Wherever possible, try to use | async in angular2 and prevent manual subscriptions!" - reason for not wanting to manually subscribe to task$ and turn it into an array.

For example:

I have this in my TS file of component:

task$: Observable<Task>;

this.task$ = this.tasksService.findTaskById(taskId);

In my service, this is the code to call firebase with AngularFire which return an observable:

import {Task} from "../model/task";
constructor(private db:AngularFireDatabase, @Inject(FirebaseRef) fb) {
   this.sdkDb = fb.database().ref();
}

findTaskById(id:string):Observable<Task> {

  return this.db.object('tasks/' + id).map(task => Task.fromJson(task));

}

In my template, I can use the value without problem in Angular2 with async pipe like so:

<md-card-title>{{(task$ | async)?.title}}</md-card-title>

But then I have a button that need the $id value inside this task$ like so:

<button md-button *ngIf="(authInfo$ | async)?.isLoggedIn()" (click)="deleteTask((task$ | async)?.$key)">Delete</button>

This won't work as click does not allow pipe inside the expression... And I DO NOT want to subscribe to task$ and turn it into member variable (best practises to keep it task$ for obserable style RXJS programming?!)

So how do I get the $key value as it is not there initially and I can not use async pipe!?

Mcreynolds answered 18/1, 2017 at 22:44 Comment(10)
Can you show your service? where tasksService.findTaskById(taskId) is called?Prefrontal
Hi @pezetter ! I added the service call. It is basically an angularFire2 observable. The task$ is working fine. It is (click) not allow async pipe is the issue. I have no idea how to avoid that and not use subscribe.Mcreynolds
What is the purpose of the async pipe? Since the component defines task, and you are ultimately calling a method of the component (deleteTask), why do you need to pass task$ to deleteTask$? Why not just use it directly in the deleteTask method?Truism
To get the task$ in the template without using subscribe? This is standard Angular2 reactive programming code... You use async pipe to unwrap your observable? If you use subscribe, you need to manually unsubscribe the observable when it is done, which is better to let async to deal with it in template level...Mcreynolds
And @JSF, I do not have the task... the task$ it is an observable. Without subscribing to it, I won't get the task value. If that make sense...Mcreynolds
It does make sense however your approach is incorrect. You can access task$ by subscribing to in code (rather than whatever 3rd party library you are using). You must subscribe to task$ using .subscribe(). In the method deleteTask, use: this.task$.subscribe( (value) => { /*your code here*/ });Truism
@JSF, yes. This is exactly what I try to avoid. Using AngularFire2 (by google of course), I try to keep my Angular2 code reactive base - reactive programming style. And I try to NOT manually subscribe to observable as much as possible - as this is Angular2 and AngularFire best practise I believe. If you subscribing to task$, you need to unsubscribe it - which is hard to keep track when the application grow. Hope that make sense.Mcreynolds
I do not know anything about Firebase itself but I do know computer science and general programming methodologies. You have a task$ because the provider has already emitted one therefor causing the creation of this button. At that point, the observable is done with it. You need to hold onto the value of the observable if you are going to pass it at any point to a method or use it in the future. A reactive programming style doesn't preclude the use of variables.Truism
Thanks @Truism for the response. I will argue the different tho. Please read this article: briantroncone.com/?p=623 It explain why using async pipe is better than manually subscribe to observable. Look at the performance benefit part. Also, read this as I don't want to manually unsubscribe when the application grow: #41447399Mcreynolds
Let us continue this discussion in chat.Truism
K
7

For the general case of adding a click handler to anything, not just a <button>, you can use a hidden <input> element to hold the latest value of the promise/observable stream, and then access that value from other elements.

  <input #dummy style="display: none" type="text" value="{{ myAsyncSource | async }}">
  <a (click)="myFunction(dummy.value)">{{ dummy.value }}</a>
Kareenkarel answered 9/4, 2017 at 19:3 Comment(0)
D
3

You can use "*ngIf as":

<div *ngIf="(task$ | async) as task">
  <button (click)="deleteTask(task.id)">Delete</button>
</div>
Dessalines answered 27/3, 2021 at 2:52 Comment(1)
This deserves more upvotes IMO, feels the more 'angular' way of achieving thisSecularity
D
2

The way I solved the issue is that i created a variable for the button and passed its value to the handler. Also the Observable is bound to value property.

 <button type="button" #item value="{{i$ | async}}"  (click)="buttonClicked(item.value)">BUTTON</button>
Dilution answered 23/2, 2017 at 17:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.