1

I've created an Observable from "click" button event:

let editClick$ = Observable.fromEvent(this.editButton.nativeElement, 'click');

So, when "edit" button is clicked it means user is editing:

this.editing$ = editClick$.pipe(map(() => true));

The problem arises when "edit" button

  1. has not been clicked (default value would have to be false), or
  2. subscriber should receive last emitted value.

Any ideas?

Jordi
  • 20,868
  • 39
  • 149
  • 333
  • Maybe you are looking for a BehaviorSubject. https://stackoverflow.com/questions/39494058/behaviorsubject-vs-observable#40231605 – envoy Jun 05 '19 at 07:03
  • Most people are not looking for a Subject. You've got a perfectly fine Observable right there. Use one of its 80 operators to get what you want. – Bjorn 'Bjeaurn' S Jun 05 '19 at 07:14

4 Answers4

1

In order to make a default value is returned:

this.editing$.pipe(publishBehavior(), refCount());

It's similar to share() operator, but share() === multicast(() => new Subject()).refCount()

I sugguest you take a look on this link.

Jordi
  • 20,868
  • 39
  • 149
  • 333
  • Why a `publishBehavior()` instead of a `publishLast()` ? Because a `publishBehavior()` is also a `multicast()` https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/publishBehavior.ts. And so is a `publishLast()` by the way, but it seems to link up more with what you want; the last value. – Bjorn 'Bjeaurn' S Jun 05 '19 at 09:07
  • `publishLast` needs source completes. When I wrote out I want to receive last emitted value, I meant "the most recent emited value"... – Jordi Jun 05 '19 at 09:11
  • Alright. A `publishReplay(1)` would achieve exactly the same, and limits its buffer to one. My main issue with this is, you have a nice functioning hot observable because you use a `creation operator`, and you're using another "internal" subject to achieve this. Honestly, I don't know which operator would give you the `replay` behavior you're looking for without creating a subject interally. At least it's not a manual one as a consolation prize. ;-). I upvoted it nonetheless, although I'm not entirely sure if the `refCount()` is required. Any of the `publish()` operators are `multicast()`. – Bjorn 'Bjeaurn' S Jun 05 '19 at 09:20
0

I read you second requirement as "when a new subscriber subscribes, he wants to get the latest value immediately". This will do it then:

this.editing$ = editClick$.pipe(
  map(() => true),
  startWith(false), // <- this will be the starting value
  shareReplay(1) // <- the last emitted value will be saved and emitted immediately when a new subscriber subscribes
);

If this is not what your second requirement is about, please clarify what exactly your use case is.

dummdidumm
  • 4,828
  • 15
  • 26
  • How does shareReplay() help here? It doesn't complete. – Bjorn 'Bjeaurn' S Jun 05 '19 at 07:19
  • He wants to receive the last emitted value. shareReplay accomplishes that. – dummdidumm Jun 05 '19 at 07:21
  • It... does not? It makes the Observable hot. The `editClick$` is a `fromEvent` stream which, by default, never completes. `shareReplay()` just makes it a `ReplaySubject` under the hood. The only thing this will accomplish is emit the last known value upon subscription. This is highly inefficient. – Bjorn 'Bjeaurn' S Jun 05 '19 at 07:34
  • This is not highly inefficient, it's exactly what he wants: I read "subscriber should receive last emitted value" as "save last value and emit it for new subscribers". So having one value cached is not evil but needed in this use case. Please remove your downvote. – dummdidumm Jun 05 '19 at 07:37
  • I'll let @Jordi describe if your assumption is correct before I will change anything. The recommendation of using `Subjects` without proper cause (he's already using a perfectly fine creation operator for his Observable, there is no need for a Subject here) seems like bad practice to me and is being done way to much. – Bjorn 'Bjeaurn' S Jun 05 '19 at 07:44
  • @Bjorn'Bjeaurn'S , @user10196188 is on the right path. I've solved that using `publishBehavior() + refCount()` operators. – Jordi Jun 05 '19 at 08:41
0

I'm quite happy with your choice to create an Observable out of fromEvent (be advised, the syntax you're showing signals and older (deprecated) version of RxJS, what version are you running? I will be using the latest version for my examples)

I think for your first question, you will want startWith().

https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/startWith.ts

You'll use it like this:

fromEvent(this.editButton.nativeElement, 'click').pipe(
    map(() => true),
    startWith(false)
)

The second question is a bit more complicated, as a fromEvent() Observable does not complete by default. This gives you a couple of options and it would heavily depend on your usecase.

The operators that come to mind: take(x) where x is a number of values to accept. If this is the first value, first() works as well.

debounceTime(ms), which buffers all inputs and only emits after the debounce miliseconds has passed. You could add a take(1) after that in combination to make it complete after.

Perhaps if you could expand on your use case, we could help you determine which operators or combination of these would help.

Bjorn 'Bjeaurn' S
  • 3,861
  • 2
  • 18
  • 29
0

I guess you don't want startWith(false) because the subscriber might get 2 emits, and you only want 1 emit. How about this:

this.editing$ = editClick$.pipe(
mapTo(() => true),
defaultIfEmpty(false),
);
Smola
  • 91
  • 1
  • 2