The observable in RxJS is a class. For that reason, it is possible to extend it and make subclasses, such as my age observable here, for instance. My age observable has a constructor here that takes the initial age-- we're passing here 20 -- and then it will set that as the field here, age. When we subscribe to this custom observable, we're sending that initial age.
This is what happens here when we subscribe. We're going to see that my age is 20, like that. My custom class here has also this setter, so I can set the age, but, as you noticed when I set it 21, nothing changes here in the behavior of the observable. This is starting to look a bit complicated.
What would we need to do in order for that age to actually appear is we would need to, for instance, set the observer of the class to this observer once we subscribe. Now we have a reference to the observer, and then we could do something like this observer should receive the next age that was just set, so maybe this works now.
Also, this is not ideal because what if we do the set age before the subscribe? Then, technically, here observer is not defined yet because it's only defined once we subscribe, so this is going to break. As we see, it broke.
It started to get complicated, and the reason is that you shouldn't usually extend the observable to make your own custom observables. There are reasons why this is bad idea. First of all, it is complicated. It's hard to get this right so that we don't get errors, such as this case.
You should avoid doing this for the same reasons that you should using Observable.create, because there you may violate the observable contract, like, for instance, what if you have an error here, and then after that try to send through observer.next()? That violates the observable contract.
Another reason is that usually observables don't carry state between different subscribers, but here we're definitely carrying this common state of the current observer throughout different subscribers. If we would subscribe more times here, we would have conflicts. We would have one subscribe trying to set the observer and the other subscribe setting it again, so there's this conflict.
As you can see, it's not the case here. We shouldn't be trying to do this. To build special observables, we just compose them with the operators that we know, not by extending the observable, because we'd get into very tricky situations.
That said, there are legitimate cases for extending the observable. One of them is, for instance, logging. Here we have an example of that. To give some context, here this observable ticks every 100 milliseconds, and it looks like this over time. Once we passed that through this chain of operators, we're multiplying each of those by 10, so we get 10, 20, 30.
We filter for only numbers bigger than 15, so we get 20 and 30. Then we count the amount of those, so we're going to get two at the end. That's why it's going to show a pop-up saying two, like that. Here we can make our own custom observable by extending that. Then we can use the lift method, which is very interesting.
We also need to extend a little bit more stuff, like we need to make a log subscriber and log operator, but the idea here is that we want to inject a special behavior throughout the whole chain. Here we want to inject console.log() whenever next happens, or also when an error happens, or when complete happens.
I recreated this observable here but now as a log observable. It's an observable, so it takes the subscribe function here. We can call observer.next() every 100 milliseconds and observer.complete(). This observable is a log observable. The interesting thing is that once you call map on a log observable, the result of this will be also a log observable.
Also, once you call filter on a log observable, you're going to get out a log observable and so forth. How did we get that is through lift. Lift allows us to inject a custom behavior throughout the chain of operators. Basically, we're creating a new observable, which is also a log observable.
That's how we get those new ones, and that's what we return here in the end, but we're telling this one here that its source is the current one, so this keyword. We're wrapping the operator, such as map, in this log operator. That's how the log operator is going to create a log of subscriber and then finally inject that custom behavior in the chain.
What we get is that, once I hit run here, it's going to log each of these operations in the console log like that. We saw here 10 as the output of map. 10 didn't pass the filter because it's less than 15. Then we saw 20 as the output of map. 20 also passes the filter, so that's why we see 20 again. Then we see 30 from the map. 30 passes the filter. We see 30 again.
Then the output of map completes. That also propagates the filter, so filter completes. Now that finally this one has completed, count will kick in, so it's going to count all of the events that happened. It saw just two, so that's why count now emits two. Then finally count completes.
It's very nice that we were able to inject this logging behavior into this chain. Notice that we didn't touch any code related to this part. We only changed what was the source here, what was this observable. If we go back to this, as you see, now we don't have logging anymore.
All that was needed was just to change this source, because this log observable will propagate down this chain. That's one good use case when you should extend the observable -- when you want to inject a custom behavior throughout all of the chain of operators.
That said, it's not that common to extend the observable for this purpose, and it's also rather tricky because you need to make your own log operator that wraps other operators and also a log subscriber. You need to know some bits of the internal logic in RxJS.
Overall, I recommend that you don't extend the observable class but just stick to operators that allow you to compose and easily build observables.