88
private customer: Subject<Object> = new BehaviorSubject<Object>(null);

setCustomer(id, accountClassCode) {
    this.customer.next({'id': id, 'accountClassCode': accountClassCode});
}

getCustomer() {
    return this.customer.asObservable();
}

I'm using this part of code but I'm getting an error that can not find id of null. Is there any solution to get initial value that is not null?

Vladimir Zdenek
  • 2,270
  • 1
  • 18
  • 23
None
  • 8,817
  • 26
  • 96
  • 171

5 Answers5

176

The purpose of BehaviorSubject is to provide initial value. It can be null or anything else. If no valid initial value can be provided (when user id isn't known yet), it shouldn't be used.

ReplaySubject(1) provides a similar behaviour (emits last value on subscription) but doesn't have initial value until it is set with next.

It likely should be

private customer: Subject<Object> = new ReplaySubject<Object>(1);
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 6
    The default is 1 if you don't specify it, so `new ReplaySubject()` is better. Specifying the type does nothing at runtime btw, but provides type safety at compile time. – Simon_Weaver Aug 13 '18 at 19:48
  • If you're interested here's the source code : https://github.com/ReactiveX/rxjs/blob/master/src/internal/ReplaySubject.ts – Simon_Weaver Aug 13 '18 at 19:49
  • 1
    Prefer `ReplaySubject.create(1)` – Roman Tikhonov Jan 18 '19 at 16:23
  • @RomanTikhonov You likely don't want to do that. It's not the same thing. See https://stackoverflow.com/a/45912937/3731501 . – Estus Flask Jan 19 '19 at 18:09
  • 18
    @Simon_Weaver the current default (Nov 2019) is infinity. setting 1 is required. https://rxjs-dev.firebaseapp.com/api/index/class/ReplaySubject – Amit Portnoy Oct 31 '19 at 08:26
  • Thank you a lot. How can I map pairwise result to object with keys: {one: value, two: value}? –  Feb 19 '21 at 11:23
  • 1
    @Mamkad Likely with basic destructuring, `map(([one, two]) => ({ one, two }))` – Estus Flask Feb 19 '21 at 12:19
  • How to get the `Data` out? – lolelo Jan 15 '22 at 09:26
  • @lolelo The same way as with any other observable, by using subscribe() or converting it to a promise. – Estus Flask Jan 15 '22 at 09:43
  • Since `BehaviorSubject` where updating for every same value. I made a `Set` extension method that checks the current value. Also I believe I need current value in other places – lolelo Jan 15 '22 at 09:49
  • @lolelo Was the question about ReplaySubject or BehaviorSubject? I'm not totally sure what is your case, but BehaviorSubject has `value` property, exactly because it always has a value. Other subjects and observables don't. In case you want to be able to always get current value from other kinds of subjects, you need to extend them and expose it in the same way that BehaviorSubject does, https://github.com/ReactiveX/rxjs/blob/master/src/internal/BehaviorSubject.ts#L37 – Estus Flask Jan 15 '22 at 10:46
  • I would like to have access to current value but I don't want the initial subscribe trigger. Thanks. I just realized this was marked as rxjs I am .Net. Unfortunately the C# implementation loos more complicated: https://github.com/dotnet/reactive/blob/main/Rx.NET/Source/src/System.Reactive/Subjects/BehaviorSubject.cs – lolelo Jan 15 '22 at 11:17
  • @lolelo I see. I don't deal with net, but probably the option would be the same, to extend replay subject class and override next method to expose current value on an instance. – Estus Flask Jan 15 '22 at 11:39
  • 1
    @EstusFlask Thank you for your great post. You helped me allot. Attaching example from my code for others to user (tested working) private _user$= new ReplaySubject(); this._user$.next(user) ; – Uri Gross Feb 24 '22 at 13:23
7

Since the object can be null, a better choice is to infer the type like this

 private customer = new BehaviorSubject<Customer|null>(null);
Bellash
  • 7,560
  • 6
  • 53
  • 86
6

Try structuring this way your service:

Service:

@Injectable()
export class MyService {
    customerUpdate$: Observable<any>;

    private customerUpdateSubject = new Subject<any>();

    constructor() {
        this.customerUpdate$ = this.customerUpdateSubject.asObservable();
    }

    updatedCustomer(dataAsParams) {
        this.customerUpdateSubject.next(dataAsParams);
    }
}

Remember to add MyService to providers.

Where you update your client (if this is the case), you do something like this:

Component (The one that triggers):

constructor(private myService: MyService) {
        // I'll put this here, it could go anywhere in the component actually
        // We make things happen, Client has been updated
        // We store client's data in an object
        this.updatedClient = this.myObjectWithUpdatedClientData;  // Obj or whatever you want to pass as a parameter
        this.myService.updatedCustomer(this.updatedClient);
    }

Component (The one that is Subscribed):

this.myService.customerUpdate$.subscribe((updatedClientData) => {
            // Wow! I received the updated client's data
            // Do stuff with this data!
        }
    );

From what I understood, you are trying to pass data from 1 component to another. You get your Client's data and send it over your App to another component, right? That's why I posted this solution.

If you are interested in other types of subscriptions, read this:

Angular 2 special Observables (Subject / Behaviour subject / ReplaySubject)

SrAxi
  • 19,787
  • 11
  • 46
  • 65
2

I have found many cases where I want the simplicity of ReplaySubject(1) but also want the value property of BehaviorSubject, i.e. it stores your most recent value for retrieval without having to subscribe. And using BehaviorSubject was always annoying given the need for an initial value that gets broadcast, a condition I rarely wanted. To that end I created my own CurrentSubject.

import { ReplaySubject } from "rxjs";

export class CurrentSubject<T> extends ReplaySubject<T> {
    value: T;

    set(value?: T) {
        this.value = value;
        this.next(value);
    }
}

I wanted to override the next method but I couldn't get that to work for some reason, so I settled on a method called set. My override attempt looked like this...

export class CurrentSubject<T> extends ReplaySubject<T> {
    value: T;

    next(value?: T) {
        this.value = value;
        super.next(value);
    }
}

I don't know why overriding didn't work.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
crowmagnumb
  • 6,621
  • 9
  • 33
  • 42
  • what was the problem? do you need to call super() inside the constructor here? – hogan Oct 16 '20 at 10:57
  • 1
    The problem with attempting to override next? I dunno, I never solved that and instead opted for the set property as I outlined. If you want to try explicitly adding a constructor and calling super() just to see if that somehow works let me know! :) – crowmagnumb Oct 16 '20 at 17:04
  • I also had the same problem when I attempted to override next. Through debugging, I noticed that without overriding, when ```next``` is called on the extension class, ```nextInfiniteTimeWindow``` is actually executed by ```ReplaySubject```, but when overriding ```next``` and calling ```super.next```, it's ```next``` from the ```Subject``` class that get called instead of ```nextInfiniteTimeWindow```. I didn't bother fixing the problem so I instead wrapped the ```ReplaySubject``` class in my own class and exposed the same methods. – user1969903 Feb 25 '21 at 06:31
  • The same is happening for me as well that I can't override the `next` method with the latest versions of TypeScript & RxJS. Any idea? – Shashank Agrawal Oct 24 '21 at 12:32
  • Explained the problem here- https://stackoverflow.com/a/69696916/2405040 – Shashank Agrawal Oct 24 '21 at 12:57
1

Another way is to use pipe to filter until a non-null value is received. It can be done with takeWhile also.

.pipe(filter(val => !!val)).subscribe(x => {});