I have an observable defined like this:
const orders$ = concat(of(undefined), vm.orders$); // Observable<any>
I upgraded to Angular 9 (major newer typescript and newer RxJS point release) and the typing of orders$ became Observable<any> instead of <Observable<Orders[]> which is what it was before. So I lost a lot of typing throughout my app.
Yes this example could be rewritten vm.orders$.pipe(startWith(undefined)) but forget about that for a minute ;-)
The change is ultimately because of(undefined) is now Observable<any> instead of Observable<undefined>. So when concatenated together the Orders[] type gets absorbed into any.
The above startWith syntax fixes the typing, as does of<undefined>(undefined) but that's clumsy.
Here's a few examples:
var concat1 = concat(of(undefined), of(123)); // becomes Observable<any>
var concat2 = concat(of('cat'), of(123)); // becomes Observable<string | number>
var concat3 = concat(of<undefined>(undefined), of(123)); // becomes Observable<number>
var concat4 = concat(of(123)).pipe(startWith(undefined)); // becomes Observable<number>
One way to 'fix' the loss of typing is by declaring the type of of explicitly, which can be made into a constant like this.
const UNDEFINED = of<undefined>(undefined);
var concat5 = concat(UNDEFINED, of(123)); // becomes Observable<number>
Interestingly there's some upcoming changes in RxJS 7 (which I'm not using) that may be related to this.
So I'm ultimately curious about the following:
what changed the behavior?
what is the recommended way to handle an undefined observable.?
if the change is a side effect of a new typescript version then would an UNDEFINED constant in the library make sense (parallel of EMPTY and NEVER)?
does RxJS 7 address this particular concern?
Related
I have an observable that performs some relatively taxing work so I need to use the shareReplay(1) operator, it's important that I have access to an emitted value immediately as well so share won't quite achieve everything I need.
The issue is that when I'm trying to cleanup and unsubscribe the source observable will keep emitting, which I believe is caused by shareReplay keeping it alive due to refCount defaulting to false. This is also the behavior I need as at application start up the reference count will jump from 1 to 0 to 1 essentially, I don't want to observable restarting on a new subscription.
Is there any way to unsubscribe and stop the source observable emitting when using shareReplay? Seems a bit strange to have no way to clean up resources when using that particular operator.
A simple example:
const first = interval(10_000).pipe(
startWith(0),
withLatestFrom(someOtherObs),
map(([i, val]) => {
// some work
}),
shareReplay(1)
)
const second = interval(5_000).pipe(
withLatestFrom(first),
take(5)
)
const main = interval(5_000).pipe(
withLatestFrom(first),
map(([i, val]) => {
// perform work
})
)
const app = from([second, main]).pipe(concatAll()).subscribe()
Basically second will complete before main will start (hence the need for shareReplay), however it will continue emitting in perpetuity due to the interval... Is there anything I can do to avoid this while achieving the same behavior... Any help would be greatly appreciated.
In your case you'll need to force the source Observable of shareReplay(1) to complete for example with takeUntil() operator.
const done$ = new Subject();
const first = interval(10_000).pipe(
startWith(0),
withLatestFrom(someOtherObs),
map(([i, val]) => {
// some work
}),
takeUntil(done$),
shareReplay(1)
)
// ...
done$.next();
I did this in F# for FRP that works simply as expected:
let print = fun a -> printf "%A\n" a
let event = new Event<_>()
let stream = event.Publish
stream |> Observable.add (fun event -> event |> print)
event.Trigger 5
Although I don't like much about event.publish system, at least, event.Trigger is somewhat straight forward to understand.
Now, I try to get to used to https://reactivex.io/
I have recognized Rx for a long time since its beta release, and I also know this API is very complicated just to do FRP, like with many "rules" like observable / observer and subjectetc., in my view, this is against KISS principle, so haven't touched.
In fact, a weird thing is for an unknown reason, I can't figure out how to do event.Trigger in Rx.
Surely, I googled a lot, and found a little information for this:
RxJS: How would I "manually" update an Observable?
According to this QA, the code for RxJS is
var eventStream = new Rx.Subject();
var subscription = eventStream.subscribe(
function (x) {
console.log('Next: ' + x);
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
var my_function = function() {
eventStream.next('foo');
}
After many trials, I finally discovered that the code below works, with luck
let stream2 = 7 |> Subject.behavior
stream2
|> Observable.map id
|> Observable.subscribe print
|> ignore
stream2.OnNext 99
However, unfortunately, this is only my Guess simply because there's no such a documentation in https://reactivex.io/documentation/subject.html and there is an external documentation http://xgrommx.github.io/rx-book/content/subjects/subject/index.html
The all I know is this code works as intended.
So, my question here is
Is this the only way to "trigger the value" based on the Rx API design?
You seem to undestand Rx basic terms: IObservable and IObserver. These API:s aren't really that complicated. F# makes it even easier since Events implement IObservable out of the box.
It seems that by trigger you mean "make an Observable emit a value" ( OnNext):
If your Observable is created from certain events, triggering such an event will produce a value.
If you want to programatically produce a value using a Subject is fine. As stated in the documentation you pasted, it implements both IObservable and IObserver. E.g. you can call OnNext and Subscribe for the object.
I suggest you consider if and why you really need to programatically produce a value in the Observable. Usually you don't since Observables are created from event sources outside your code. Some cases justify using a Subject such as writing unit tests.
I have tried to unsubscribe within the subscribe method. It seems like it works, I haven't found an example on the internet that you can do it this way.
I know that there are many other possibilities to unsubscribe the method or to limit it with pipes. Please do not suggest any other solution, but answer why you shouldn't do that or is it a possible way ?
example:
let localSubscription = someObservable.subscribe(result => {
this.result = result;
if (localSubscription && someStatement) {
localSubscription.unsubscribe();
}
});
The problem
Sometimes the pattern you used above will work and sometimes it won't. Here are two examples, you can try to run them yourself. One will throw an error and the other will not.
const subscription = of(1,2,3,4,5).pipe(
tap(console.log)
).subscribe(v => {
if(v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
Error: Cannot access 'subscription' before initialization
Something similar:
const subscription = of(1,2,3,4,5).pipe(
tap(console.log),
delay(0)
).subscribe(v => {
if (v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
This time you don't get an error, but you also unsubscribed before the 5 was emitted from the source observable of(1,2,3,4,5)
Hidden Constraints
If you're familiar with Schedulers in RxJS, you might immediately be able to spot the extra hidden information that allows one example to work while the other doesn't.
delay (Even a delay of 0 milliseconds) returns an Observable that uses an asynchronous scheduler. This means, in effect, that the current block of code will finish execution before the delayed observable has a chance to emit.
This guarantees that in a single-threaded environment (like the Javascript runtime found in browsers currently) your subscription has been initialized.
The Solutions
1. Keep a fragile codebase
One possible solution is to just ignore common wisdom and continue to use this pattern for unsubscribing. To do so, you and anyone on your team that might use your code for reference or might someday need to maintain your code must take on the extra cognitive load of remembering which observable use the correct scheduler.
Changing how an observable transforms data in one part of your application may cause unexpected errors in every part of the application that relies on this data being supplied by an asynchronous scheduler.
For example: code that runs fine when querying a server may break when synchronously returned a cashed result. What seems like an optimization, now wreaks havoc in your codebase. When this sort of error appears, the source can be rather difficult to track down.
Finally, if ever browsers (or you're running code in Node.js) start to support multi-threaded environments, your code will either have to make do without that enhancement or be re-written.
2. Making "unsubscribe inside subscription callback" a safe pattern
Idiomatic RxJS code tries to be schedular agnostic wherever possible.
Here is how you might use the pattern above without worrying about which scheduler an observable is using. This is effectively scheduler agnostic, though it likely complicates a rather simple task much more than it needs to.
const stream = publish()(of(1,2,3,4,5));
const subscription = stream.pipe(
tap(console.log)
).subscribe(x => {
if(x === 4) subscription.unsubscribe();
});
stream.connect();
This lets you use a "unsubscribe inside a subscription" pattern safely. This will always work regardless of the scheduler and would continue to work if (for example) you put your code in a multi-threaded environment (The delay example above may break, but this will not).
3. RxJS Operators
The best solutions will be those that use operators that handle subscription/unsubscription on your behalf. They require no extra cognitive load in the best circumstances and manage to contain/manage errors relatively well (less spooky action at a distance) in the more exotic circumstances.
Most higher-order operators do this (concat, merge, concatMap, switchMap, mergeMap, ect). Other operators like take, takeUntil, takeWhile, ect let you use a more declarative style to manage subscriptions.
Where possible, these are preferable as they're all less likely to cause strange errors or confusion within a team that is using them.
The examples above re-written:
of(1,2,3,4,5).pipe(
tap(console.log)
first(v => v === 4)
).subscribe();
It's working method, but RxJS mainly recommend use async pipe in Angular. That's the perfect solution. In your example you assign result to the object property and that's not a good practice.
If you use your variable in the template, then just use async pipe. If you don't, just make it observable in that way:
private readonly result$ = someObservable.pipe(/...get exactly what you need here.../)
And then you can use your result$ in cases when you need it: in other observable or template.
Also you can use pipe(take(1)) or pipe(first()) for unsubscribing. There are also some other pipe methods allowing you unsubscribe without additional code.
There are various ways of unsubscribing data:
Method 1: Unsubscribe after subscription; (Not preferred)
let localSubscription = someObservable.subscribe(result => {
this.result = result;
}).unsubscribe();
---------------------
Method 2: If you want only first one or 2 values, use take operator or first operator
a) let localSubscription =
someObservable.pipe(take(1)).subscribe(result => {
this.result = result;
});
b) let localSubscription =
someObservable.pipe(first()).subscribe(result => {
this.result = result;
});
---------------------
Method 3: Use Subscription and unsubscribe in your ngOnDestroy();
let localSubscription =
someObservable.subscribe(result => {
this.result = result;
});
ngOnDestroy() { this.localSubscription.unsubscribe() }
----------------------
Method 4: Use Subject and takeUntil Operator and destroy in ngOnDestroy
let destroySubject: Subject<any> = new Subject();
let localSubscription =
someObservable.pipe(takeUntil(this.destroySubject)).subscribe(result => {
this.result = result;
});
ngOnDestroy() {
this.destroySubject.next();
this.destroySubject.complete();
}
I would personally prefer method 4, because you can use the same destroy subject for multiple subscriptions if you have in a single page.
I have a rxjs#6 BehaviorSubject source$, I want get subvalue from source$
const source$ = new BehaviorSubject(someValue);
const subSource$ = source$.pipe(map(transform));
I expect the subSource$ also is a BehaviorSubject, but is not and How can I get the subSource$ is a BehaviorSubject ?
When a BehaviorSubject is piped it uses an AnonymousSubject, just like a regular Subject does. The ability to call getValue() is therefore not carried down the chain. This was a decision by the community. I agree (as do some others) that it would be nice if the ability to get the value after piping existed, but alas that is not supported.
So you would need to do something like:
const source$ = new BehaviorSubject(value);
const published$ = new BehaviorSubject(value);
const subSource$ = source$.pipe(...operators, multicast(published$));
You could then call getValue() on published$ to retrieve the value after it has passed through your operators.
Note that you would need to either call connect() on the subSource$ (which would make it a "hot" observable) or use refCount().
That said, this isn't really the most rxjs-ish way of doing things. So unless you have a specific reason for dynamically retrieving the value after it passes through your operator vs just subscribing to it in the stream, maybe rethink the approach?
According to rxjs marbles documentation the current behaviour for the sync groupings is the following:
'(ab)-(cd)': on frame 0, emits a and b then on frame 50, emits c and d
From the docs:
While it can be unintuitive at first, after all the values have synchronously emitted time will progress a number of frames equal to the number of ASCII characters in the group, including the parentheses
Ok, but how do I test an observable like this (using marbles or any other technique):
const observable$ = of(1, 2).concat(of(3, 4).delay(20));
Are there any workarounds?
There is a similar question on Stack Overflow but there is no answer on 'How to actually work around it and test this kind of observable'.
Thanks!
For my project I migrated to rx-sanbox where sync grouping works correct and it solved my problem.
So, in rx-sandbox this is correct:
'(ab)-(cd)': on frame 0, emits a and b then on frame 20, emits c and d
I don't know what version of RxJS you're using because you're mixing prototypical and pipable operators but it looks like RxJS 5.5.
In RxJS 5.X it's a bit clumsy. You could rewrite your test like this:
import { of } from 'rxjs/observable/of';
import { TestScheduler } from 'rxjs/testing/TestScheduler';
import { assert } from 'chai';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/delay';
const scheduler = new TestScheduler((actual, expected) => {
console.log(actual, expected);
return assert.deepEqual(actual, expected);
});
const observable$ = of('a', 'b').concat(of('c', 'd').delay(50, scheduler));
scheduler
.expectObservable(observable$)
.toBe('(ab)-(cd|)');
scheduler.flush();
See live demo (open console): https://stackblitz.com/edit/rxjs5-marble-test?file=index.ts
You know this test passes because it doesn't throw any error. Try changing any of the delays or values of next emissions and it'll throw an error.
Also have a look at this answer: How do I test a function that returns an observable using timed intervals in rxjs 5?
However, I'd strongly recommend upgrading to RxJS 6 because it makes everything much easier with cold and hot "creation" functions where you could just use const observable$ = cold('(ab)-(cd|)') to create the same sequence as you're doing with of(...).concat(...).
Testing in RxJS 6:
https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
https://github.com/ReactiveX/rxjs/blob/master/doc/internal-marble-tests.md