Filter all 'null' values from an Observable<T>
Asked Answered
J

6

32

I have a service with a subject:

@Injectable() export class UserService() {
    private currentUserSubject = new BehaviorSubject<User>(null);
    public currentUser = this.currentUserSubject.asObservable().distinctUntilChanged(); 

    ... // emitting new User    
}

Have a component I inject this service into and subscribing on updates:

@Component() export class UserComponent {
    constructor(private userService: UserService) {
        this.userService.currentUser
            .subscribe((user) => {
                // I want to see not null value here
            })
    }
}

I want to apply something to Observable<User> to filter all null values and get into subscribe only when User is actually loaded.

Jahdiel answered 27/3, 2017 at 10:6 Comment(2)
Why don't you just test if it's null in the callback? Alternatively, there is an Observable.filter.Myasthenia
I do not think it is right decision in terms of good written code. I want to "tell" that I subscribe only to not null values not to all values and filter inside #subscribe.Jahdiel
G
27

Add a filter operator to your observable chain. You can filter nulls explicitly or just check that your user is truthy - you will have to make that call depending on your needs.

Filtering out null users only:

public currentUser = this.currentUserSubject
                         .asObservable()
                         .filter(user => user !== null)
                         .distinctUntilChanged(); 
Godwit answered 27/3, 2017 at 10:14 Comment(0)
F
33

Another way to check the value exists:

public currentUser = this.currentUserSubject
                         .asObservable()
                         .filter<User>(Boolean)
                         .distinctUntilChanged(); 
Farming answered 20/4, 2018 at 11:41 Comment(8)
is there documentation for using Boolean class as a param to filter?Profitable
It is unclear to me why BooleanConstructor class can be used as a predicate function. Can someone explain this to me?Deflagrate
@ShacharHar-Shuv he's not using new here so it just performs a type conversion. See here: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… equivalent to !! but in this case it's more readable since we don't have to introduce a lambdaGillan
You'll lose type interface and rxjs 6.5 will complain about unknown typeContinental
That's a fantastic use about Class. Works like a charm!Raynell
what @Continental commented saved me hours. At this point I can't do a production build using the Angular compiler v9 because I'm using this solution.Supplication
To fix all you need is e.g. filter<number>(Boolean)Supplication
sure it works but it's not as clear as the accepted answerHoff
T
32

with rxjs@6 and typescript the recommended (readable/maintainable) way is to define the following type guard:

export function isNonNull<T>(value: T): value is NonNullable<T> {
  return value != null;
}

and augment a subject with pipe and filter:

subject.pipe(filter(isNonNull))
Thorny answered 9/2, 2020 at 10:2 Comment(3)
in typescript shouldn't the control be !== null ?Martines
the type NonNullable, refers to null or undefined, not just null. if you want to allow for undefined values (it is not what people usually want), you need to replace the check with !== null and replace NonNullable with something like: type NotNull<T> = T extends null ? never : T;Thorny
Yeah, I ended up using your solution, the only thing that surprised me is the naming (I prefer explicitly say NotNullNorUndefined), it handles null and undefined and is named 'nonNull' but that's perfect for what I wanted !Martines
G
27

Add a filter operator to your observable chain. You can filter nulls explicitly or just check that your user is truthy - you will have to make that call depending on your needs.

Filtering out null users only:

public currentUser = this.currentUserSubject
                         .asObservable()
                         .filter(user => user !== null)
                         .distinctUntilChanged(); 
Godwit answered 27/3, 2017 at 10:14 Comment(0)
K
13

It's as simple as:

filter(user => !!user),

So would filter these falsy values (link to Falsy on MDN). or alternatively cast to a boolean like this:

filter(user => Boolean(user)),

If user evaluates to false, the filter will not be passed.


Update (after Mick his comment on type checking):

If you want to add some more proper typescript solution you could use a type predicate (user-defined type guard). See documentation on this here on https://www.typescriptlang.org/ .

Assume you have a class User. Example:

const isUser = (user: null|User): user is User => {
  // Using an instance of check here, which is not the most performant
  // but semantically most correct.
  // But you could do an alternative check and return a boolean instead
  return user instanceof User;
}

If you now do:

filter(user => isUser(user));

Typescript will understand that you have an object of type User after the filter.

Klein answered 15/5, 2020 at 16:33 Comment(4)
The problem with that is that Typescript doesn't know that user is now definitely definedFortunna
@Mick, you can use a type predicate for this. Check documentation here. I added an example to my answer.Klein
I think your answer is obsolet. The one of Amit Portnoy is IMO superior because it provides a generic type while you use the "evil" any and you function is only usable with the User type.Fortunna
@Mick, sure, just tried to help out with an update. Changed any to null|User which is more in line with the question. Sorry for wasting your time. Have a nice day.Klein
G
4

Another option for rxjs@6 is to create another wrapping operator of skipWhile.

// rxjs.utils.js
export const skipNull = () => <T>(source: Observable <T>): Observable<T> => source.pipe(skipWhile(value => value === null));

Usage:

this.observable.pipe(skipNull()).subscribe(...)
Gastrostomy answered 10/8, 2021 at 11:37 Comment(1)
This is very useful because it's reusableEduino
M
0

Why do you want to use to BehaviorSubject in the first place?

What you need is to define your state as ReplaySubject ensuring that you'll get the data when the user is updated.

import { BehaviorSubject } from 'rxjs';

const subject = new BehaviorSubject(123);

// two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);

// two subscribers will get new value => output: 456, 456
subject.next(456);

VS Replay subject:

const sub = new ReplaySubject(3);

sub.next(1);
sub.next(2);
sub.subscribe(console.log); // OUTPUT => 1,2
sub.next(3); // OUTPUT => 3
sub.next(4); // OUTPUT => 4
sub.subscribe(console.log); // OUTPUT => 2,3,4 (log of last 3 values from new subscriber)
sub.next(5); // OUTPUT => 5,5 (log from both subscribers)
Monsour answered 30/8, 2022 at 23:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.