1

I am using RxJs Behaviour subjects in following manner as below.

=> Made a common service for all behaviour subjects like sample code below

@Injectable()
export class ProjectJobSelectionService {

public postTypeSubject = new BehaviorSubject(PostType.Project);
public selectedProjectSubject = new BehaviorSubject(null);
public selectedJobsiteSubject = new BehaviorSubject(null);
public selectedJobSubject = new BehaviorSubject(null);
public addJobsiteSubject = new BehaviorSubject(null);

}

I have one side bar component which needs to be refreshed everytime on the next method or any new value gets emitted from various components.

side bar is the common component where all these subjects are subscribed in ngOnInit method and these subjects are being next (I mean emit values from various respected components)

Problem

Each and every time these subjects gets called by itself and the respected logic gets called when no respective components are instantiated or are not in life cycle.

was not able to retrive the cause of it also tried to comment the code from where it gets next still gets called automatically.

Any help would be highly appreciated.

kushal Baldev
  • 729
  • 1
  • 14
  • 37
  • 1
    `BehaviorSubject` automatically emits the most recent `next` (or the default value) on every subscription so maybe that's the problem you're seeing? – martin Apr 30 '21 at 08:48
  • yeah so I mean If have like this public selectedProjectSubject = new BehaviorSubject(null); would it get emitted by itself if yes then how to overcome it? as I want that subjects effectiveness only when it nexts – kushal Baldev Apr 30 '21 at 08:52
  • I'm sorry I think I don't follow. I think I'd need to see a demo of what it does and what you want it to do. – martin Apr 30 '21 at 09:12
  • Try to use Subject instead of BehaviorSubject, BehaviorSubject emite last value ( in first time default value you has been created it, null in this case) always you subscribe, Subject only emit value after you subscribe, you'll only get new values emited – Yoan Asdrubal Quintana Ramírez Apr 30 '21 at 17:10

3 Answers3

2

The point of all types of Subjects (including BehaviourSubject) is that they're hot, i.e. they will emit even if there is no subscriber. If you do not want this behaviour, you should consider using a (cold) Observable instead, or use a Subject (or similar) which doesn't have an initial value.

Lmbrtz
  • 91
  • 6
  • I'd rephrase the first statement: The point of `BehaviourSubject` isn't that they're hot (`Subject` is also hot) but that it takes a default value and it can hold/store the current value and emit it immediately to future subscribers. I agree with the rest of the answer. – ruth Apr 30 '21 at 12:45
  • True, thanks for pointing that out. I've rephrased the sentence. – Lmbrtz Apr 30 '21 at 14:05
2

Sounds like you do not want the default value that a BehaviourSubject provides.

There are two solutions you could try:

1) Filter Null Values

Wherever you subscribe to a BehaviorSubject, you can ignore null values like this:

selectedJobsiteSubject.pipe(
  filter(v => v != null)
).subscribe(
  /* ... */
);

2) No Default Values

A BehaviourSubject without a default value is just a ReplaySubject that replays the most recent emission upon subscription.

So you could change your service to look like this:

@Injectable()
export class ProjectJobSelectionService {

  public postTypeSubject = new BehaviorSubject(PostType.Project);
  public selectedProjectSubject = new ReplaySubject(1);
  public selectedJobsiteSubject = new ReplaySubject(1);
  public selectedJobSubject = new ReplaySubject(1);
  public addJobsiteSubject = new ReplaySubject(1);

}
Mrk Sef
  • 7,557
  • 1
  • 9
  • 21
0

I'm sure it breaks RXJS orthodoxy, but my preferred solution to this problem (so I don't have to reconfigure everything to use Observables) is to create a new type of subject that blends the best of both worlds. This subject is like a ReplaySubject but has a value field just like a BehaviorSubject. It can be initialized without an initial value (an annoying aspect of BehaviorSubjects for me) and can be created from the output of a .pipe call on another Subject or Observable, using the class from() method (surprisingly common use case in my code at least). Hope it helps!

export class QueryableReplaySubject<T> extends ReplaySubject<T> {
  /**
  * The `bufferSize` determines how many of the last emitted values this subject replays to new subscribers.
  */
  constructor(bufferSize: number = 1,) {
    super(bufferSize);
  }

  get value() {
    let returnValue: T;
    this.pipe(take(1)).subscribe(value => {
      returnValue = value;
    });
    //this works only because the subscription is processed synchronously and the underlying replay
    //subject emits immediately.
    // @ts-expect-error
    return returnValue;
  }

  /**
   * If `forwardCompletion` is true (default is true), the resulting subject will complete when the source observable completes.
   */
  static from<T>(observable: Observable<T>, bufferSize = 1, forwardCompletion = true) {
    let subject = new QueryableReplaySubject<T>(bufferSize);
    if (forwardCompletion) {
      observable.subscribe({
        complete: () => {
          subject.complete();
        },
        error: err => subject.error(err),
        next: val => subject.next(val)
      });
    } else {
      observable.subscribe({
        error: err => subject.error(err),
        next: val => subject.next(val)
      });
    }
    return subject;
  }
}
Marko Bakić
  • 103
  • 1