38

Is there a specific idiom or utility used to filter undefined from RxJS observables? This code has the behavior I want:

obs.pipe(filter(x => x !== undefined))

Some alternatives are

obs.pipe(filter(x => x)) // for all falsy values

obs.pipe(filter(Boolean)) // for falsy variables using Boolean ctor
2
  • 1
    You can write a custom operator for this but it probably won't be much shorter than filter(Boolean) even though this will filter out also all false, '' and so on. Commented Sep 18, 2019 at 19:46
  • obs.pipe(filter(x => x !== undefined)) is what I would use, simple and easy to read Commented Sep 19, 2019 at 3:17

6 Answers 6

69

Strictly speaking, using filter alone to remove undefined values doesn't do everything that you need. It filters out the undefined values, but does not change the type to indicate that undefined is no longer a possible value.

In the example:

const obs = new Subject<string | undefined>;
const stringObs = obs.pipe(filter(x => x !== undefined))

the stringObs observable still has the type Observable<string | undefined>.

To get round this problem, you need to use:

const stringObs = obs.pipe(filter(x => x !== undefined) as OperatorFunction<string | undefined, string>)

This is only really an issue if you use strict null checks, but if you do (and arguably you should) then creating a custom operator suddenly makes a lot more sense! Something like

function filterNullish<T>(): UnaryFunction<Observable<T | null | undefined>, Observable<T>> {
  return pipe(
    filter(x => x != null) as OperatorFunction<T | null |  undefined, T>
  );
}

does the job.

You can then use:

const stringObs = obs.pipe(filterNullish())

and the type of stringObs will be Observable<string>. I was quite impressed that Typescript manages to infer the type of T correctly.

Sign up to request clarification or add additional context in comments.

2 Comments

This answer is great, you've taken a straightforward question and gone beyond what was being asked by applying real experience to fix the subtle typing issue too! My only tiny niggle is your snippet is relying on loose equality checking with the filter(x => x != null) as I like to make sure that strict equality checking is used, so I think I'd use filter(x => !!x) or filter(x => Boolean(t)) instead.
x != null seams a better operator as it will filter null and undefined values. Which is different from !!x or Boolean(x) as it filter null , undefined but also 0 , false , '' (empty string) which may lead to unexpected behaviors...
21

To help typescript understand that undefined values have been removed, one way is to create a custom operator. Another way is possible via a user-defined type guard.

function isDefined<T>(arg: T | null | undefined): arg is T extends null | undefined ? never : T {
  return arg !== null && arg !== undefined;
}

const obs = from(['hello', null, undefined]);
const filtered: Observable<string> = obs.pipe(filter(isDefined));

3 Comments

This should be included in the RxJS operators.
Why isn't it enough to do arg is T? What does the extends part help with?
@MikkelR.Lund The extends part makes it work even when T = undefined. Otherwise the compiler would think that isDefined(undefined) proves that arg is undefined which is not correct.
5

Rxjs operators are considered low-level and are intended to be combined in a readable way to create a resultant observable that you can use predictably. Having a 'utility' do that for you isn't exactly what I would (humbly) call "the rxjs way". If by 'idiom' you mean like a convention, your code example is basically what you're looking for.

I myself use that exact filter pattern on a regular basis, to the point where I've considered making a custom operator; however, rather than this:

obs.pipe(filter(x => x !== undefined))

You'll just end up with this:

obs.pipe(notUndefined()) // or hasValue() or whatever you want to name it...

There are not a lot of savings here. I would argue that you're not even getting savings in readability (or a totally marginal one, at that), which is why I've never gotten around to it. Another justification for not mobilizing on that is that it is also common to filter against a boolean value, which then starts to make you wonder if you should combine the two to avoid having too many confusing/like operators, all of which could be easily created using filter anyways, etc etc...

Long story short, I've thought about this exact issue a lot, and would argue that you simply use the code example you provided. It's "correct". It's "rx-js-y". It's readable.

1 Comment

I like this answer quite a bit. The accepted answer for the question to right to have been accepted, but I think realistically most people would be just fine doing what's suggested here. If I was a solo dev working on a personal project, sure I might make the function for my own amusement, but I don't think it's necessary here.
3

NOTE: Used in production open source project.

Step 1: Use isNonNullable

readonly filePath$ = this.pickFilesResult$.pipe(
    map((pickMediaResult) => pickMediaResult.files[0]?.path),
    isNonNullable()
);

Step 2: In your project add src/utils/rx-operators.ts

import { Observable, filter } from 'rxjs';

export function isNonNullable<T>() {
  return (source$: Observable<null | undefined | T>) =>
    source$.pipe(
      filter((v): v is NonNullable<T> => v !== null && v !== undefined)
    );
}

Comments

1

As the latest version of the typescript expects a predicate of boolean, it is not supporting previous falsy filters like:

filter(x => x)

in that case you might want to use:

filter(x => !!x)

Comments

0

I think i'm right in saying you can just use:

obs.pipe(filter(x => x))

which is equal too:

obs.pipe(filter(x => x !== null && x !== undefined))

2 Comments

Sorry, but these are not equal. They might produce the same result in a fairly good amount of cases, but if the observer return values that can be false, they're not equal.
This isn't simply an undefined test. I feel like a lot of devs confuse undefined as meaningless, but its meaning is very important. Undefined means there has not been a value set, or that a value was explicitly unset. Null actually is a value. false and 0 are also values, but in this particular filter all of them filter out the same way. So, your operator is simply checking for falsey values - it does not check for undefined specifically - and this is a really, really important distinction. A good example where this could fail is "am I on page 0, or have I even tried loading a page"?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.