9

I have a need to set the value of my BehaviorSubject without triggering a next call to any subscriptions.

I tried doing this:

this.mySubject = new BehaviorSubject(newVal);

but that removes all of the subscriptions as well.

this.mySubject.value = newVal;

doesn't work because .value is readonly.

Is there any way to accomplish this?

Edit: for those asking why I need to do this...

This is an Ionic 4 app, so there is the root list page that calls 2 other child pages (a detail view and an edit page). The first naviagtion into the view page needs the model that was selected. The list page sets the initial value of this model into the BehaviorSubject, then navigates into the view page. However, the setting of this initial value triggers the refresh of the list, which I don't want to do. I just want to init the value, then listen in case the edit page changes it and THEN refresh my list.

Scottie
  • 11,050
  • 19
  • 68
  • 109
  • 1
    uh? why would you want to do that? the whole purpose of a subject is to emit to all its subscribers whenever its value is changed... – Jota.Toledo Feb 04 '19 at 17:19
  • hey, please read this post about BehaviourSubject by core contributor ben lesh: https://stackoverflow.com/a/45227115/7247920 .. basically, you shouldn't use `value` at all ;-) – ggradnig Feb 04 '19 at 17:36
  • @Jota.Toledo Updated the question with an explanation of why I need to do this. – Scottie Feb 04 '19 at 17:49

2 Answers2

16

I'm curious to know why you would actually want to do this but one option you could consider is to include as part of your value a flag that indicates whether updates should be propagated and then have all of your subscriptions derive from a filtered view of the Subject.

this.mySubject.next({propagate: false, value: 42});

makeObservable() {
   return this.mySubject.pipe(filter(x => x.propagate));
}
Jesse Carter
  • 20,062
  • 7
  • 64
  • 101
  • Basically I have a large dataset that will be refreshed from the server when the model changes. It's an Ionic app, so this is the root page that calls 2 other child pages (a detail view and an edit page). The first naviagtion into the view page needs the model that was selected. The list page sets the initial value of this model, then navigates into the view page. However, the setting of this initial value triggers the refresh of the data, which I don't want to do. It's probably just as easy to set a flag in the list page that is false on init so it won't refresh. – Scottie Feb 04 '19 at 17:44
  • I thought about exactly the same solution as yours as well. – martin Feb 04 '19 at 21:12
  • simple but elegant, great answer @Jesse Carter – Maurice Mar 07 '21 at 17:15
  • this is amazing! – Michael Ashefor Feb 19 '22 at 23:27
1

BehaviorSubject isn't that complicated. You could create your own implementation :)

import { Subject } from './Subject';
import { Subscriber } from './Subscriber';
import { Subscription } from './Subscription';
import { SubscriptionLike } from './types';
import { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';

/**
 * A variant of Subject that requires an initial value and emits its current
 * value whenever it is subscribed to.
 *
 * @class BehaviorSubject<T>
 */
export class BehaviorSubject<T> extends Subject<T> {

  constructor(private _value: T) {
    super();
  }

  get value(): T {
    return this.getValue();
  }

  /** @deprecated This is an internal implementation detail, do not use. */
  _subscribe(subscriber: Subscriber<T>): Subscription {
    const subscription = super._subscribe(subscriber);
    if (subscription && !(<SubscriptionLike>subscription).closed) {
      subscriber.next(this._value);
    }
    return subscription;
  }

  getValue(): T {
    if (this.hasError) {
      throw this.thrownError;
    } else if (this.closed) {
      throw new ObjectUnsubscribedError();
    } else {
      return this._value;
    }
  }

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

either just copy the above, or create a subclass, that exposes a setValue

export class SettableBehaviorSubject<T> extends BehaviorSubject<T> {
  setValue(value: T): void {
    this._value = value;
  }
}

the source of BehaviorSubject is here: https://github.com/ReactiveX/rxjs/blob/master/src/internal/BehaviorSubject.ts

Leon Radley
  • 7,596
  • 5
  • 35
  • 54