The RxJS of operator is one of the most used ones. If you just want to create a simple Observable you are probably opting for of. But at the same time it’s a strange Observable because it wraps plain values instead of async operations.
RxJS of explained 🎓
The of operator converts its arguments to an Observable sequence.
of<T>(...args: (SchedulerLike | T)[]): Observable<T>
Important here is that it’s a sequence and not a single value. That means below code emits 3 next notifications. The emissions happen all in the same time frame. And after everything is emitted the Observable sends the complete notification.
of(1,2,3).subscribe({ next: console.log })
// 1
// 2
// 3
What problems does the RxJS of operator solve? 🚧
In my experience the of operator is mainly used in two areas:
- In specs developers tend to use of to create Observables they need in their tests. While this is an approach that can work there are most probably other alternatives. In most cases a cold rxjs-marbles Observable fits better and lets you control when the values are emitted and when the Observable completes. When using the of operator in testing, your values are emitted immediately on subscription together with the complete notification.
- Sometimes you have an RxJS structure where you might return an Observable inside another Observable. If you then have a condition and want to return a default value in some second case of could help.
How to test the of operator🚦
RxJS marbles is probably the way to go if you have code involving the of operator. If you check below test you can clearly see that the values are emitted in the same time frame.
it('should emit and complete', () => {
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
const numbers$ = of(1,2,3);
expectObservable(numbers$).toBe('(123|)', {'1': 1, '2': 2, '3': 3});
});
});
Also the completion happens at time frame 0 in our test. This can be dangerous when you are using the operator to mock Observables in your tests. The following test demonstrates clearly what can go wrong:
it('should only be rarely used for testing', () => {
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
// numbers$ is actually something involving: interval(1).pipe(take(2));
// but here it's 'mocked' with of
const numbers$ = of(0,1);
expectObservable(numbers$).toBe('-01', {'0': 0, '1': 1});
});
});
Compare it to a test using rxjs-marbles cold Observable where we can clearly define the time frames:
it('should be replace by rxjs marbles cold observable', () => {
testScheduler.run((helpers) => {
const { expectObservable, cold } = helpers;
const numbers$ = cold('-0(1|)');
expectObservable(numbers$).toBe('-0(1|)');
});
});
Exercise for the of operator 💪
Combine of with another RxJS construct to emit the values at different time frames.