30

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.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
fasth
  • 2,232
  • 6
  • 26
  • 37
  • 3
    Why don't you just test if it's null in the callback? Alternatively, there is an `Observable.filter`. – jonrsharpe Mar 27 '17 at 10:12
  • 3
    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. – fasth Mar 27 '17 at 10:14

6 Answers6

31

Another way to check the value exists:

public currentUser = this.currentUserSubject
                         .asObservable()
                         .filter<User>(Boolean)
                         .distinctUntilChanged(); 
jenson-button-event
  • 18,101
  • 11
  • 89
  • 155
ackuser
  • 5,681
  • 5
  • 40
  • 48
28

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))
Amit Portnoy
  • 5,957
  • 2
  • 29
  • 30
  • in typescript shouldn't the control be !== null ? – Romain Bitard May 13 '20 at 15:58
  • 3
    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 extends null ? never : T;` – Amit Portnoy May 14 '20 at 08:11
  • 1
    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 ! – Romain Bitard May 15 '20 at 08:54
26

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(); 
snorkpete
  • 14,278
  • 3
  • 40
  • 57
12

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.

Wilt
  • 41,477
  • 12
  • 152
  • 203
  • The problem with that is that Typescript doesn't know that user is now definitely defined – Mick Oct 20 '21 at 12:52
  • @Mick, you can use a type predicate for this. Check documentation [here](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates). I added an example to my answer. – Wilt Oct 20 '21 at 15:35
  • 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. – Mick Oct 20 '21 at 15:47
  • @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. – Wilt Oct 20 '21 at 16:20
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(...)
noamyg
  • 2,747
  • 1
  • 23
  • 44
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)