djechlin
djechlin

Reputation: 60758

Filter undefined from RxJS Observable

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

Upvotes: 37

Views: 26945

Answers (6)

sultanmyrza
sultanmyrza

Reputation: 5392

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)
    );
}

Upvotes: 2

piedar
piedar

Reputation: 2711

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));

Upvotes: 21

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)

Upvotes: 1

Chris
Chris

Reputation: 27

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))

Upvotes: 0

Simon Williams
Simon Williams

Reputation: 781

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.

Upvotes: 68

dudewad
dudewad

Reputation: 13933

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.

Upvotes: 4

Related Questions