322

I have an Angular 2 service:

import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject}    from 'rxjs/Subject';

@Injectable()
export class SessionStorage extends Storage {
  private _isLoggedInSource = new Subject<boolean>();
  isLoggedIn = this._isLoggedInSource.asObservable();
  constructor() {
    super('session');
  }
  setIsLoggedIn(value: boolean) {
    this.setItem('_isLoggedIn', value, () => {
      this._isLoggedInSource.next(value);
    });
  }
}

Everything works great. But I have another component which doesn't need to subscribe, it just needs to get the current value of isLoggedIn at a certain point in time. How can I do this?

williamsandonz
  • 15,864
  • 23
  • 100
  • 186

13 Answers13

519

A Subject or Observable doesn't have a current value. When a value is emitted, it is passed to subscribers and the Observable is done with it.

If you want to have a current value, use BehaviorSubject which is designed for exactly that purpose. BehaviorSubject keeps the last emitted value and emits it immediately to new subscribers.

It also has a method getValue() to get the current value.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • what is the difference than simply doing $behaviorSubject.value rather than $behaviorSubject.getValue() ? From the source code, the getValue() method does some checking. https://github.com/Reactive-Extensions/RxJS/blob/master/src/core/subjects/behaviorsubject.js – schrodinger's code Jun 13 '17 at 02:04
  • To me it looks like the main difference is that with `getValue()` an exception will be thrown when the last value was an error, while `value` will onky give the last non-error value. – Günter Zöchbauer Jun 13 '17 at 05:26
  • 1
    Hi, it is my bad, they are the same thing now in the rxjs 5. The source code link above was referring to rxjs4. – schrodinger's code Jun 15 '17 at 05:35
  • 163
    Important note from the author of RxJS 5: Using `getValue()` is a HUGE red flag you're doing something wrong. It's there as an escape hatch. Generally everything you do with RxJS should be declarative. `getValue()` is imperative. If you're using `getValue()`, there's a 99.9% chance you're doing something wrong or weird. – Ben Lesh Jul 21 '17 at 00:01
  • 5
    @BenLesh what if I have a `BehaviorSubject(false)` and like to toggle it? – bob Oct 27 '17 at 21:47
  • @bob just emit a new value – Günter Zöchbauer Oct 28 '17 at 06:50
  • 3
    @GünterZöchbauer What I meant was: when emitting a new boolean, I need to know the current value to toggle it. In that case, is using `getValue()` justified? – bob Oct 28 '17 at 20:15
  • you can also store the value in a field, but I think using getValue is fine – Günter Zöchbauer Oct 28 '17 at 20:43
  • 1
    You don't need getValue to toggle it. Just use `foo$.pipe(take(1)).subscribe(val => foo$.next(!val))`. Since it's a behavior subject, this will work instantly as you get the last emitted value on subscription. – Ingo Bürk Nov 25 '17 at 06:33
  • 13
    @BenLesh getValue() is very useful for say, doing an instantaneous action, like onClick->dispatchToServer(x.getValue()) but don't use it in observable chains. – FlavorScape Apr 26 '18 at 00:37
  • 4
    @IngoBürk The only way I can justify your suggestion is if you didn't know that `foo$` was a `BehaviorSubject` (i.e. it is defined as `Observable` and maybe it's hot or cold of from a deferred source). However since you then go on to use `.next` on `$foo` itself that means your implementation relies on it *being* a `BehaviorSubject` so there's no justification for not just using `.value` to get the current value in the first place. – Simon_Weaver Jun 12 '18 at 03:04
  • @Simon_Weaver it relies on it being a Subject, not a BehaviorSubject. `getValue`, on the other hand, does tie it to the specific subject implementation. I've seen it over and over again that Behavior* is used where Replay* would be appropriate, and this makes refactoring that very difficult. – Ingo Bürk Dec 29 '20 at 17:30
  • 1
    Thanks for this, RxJS is pretty hard to understand, but once you get it, you cannot do without it haha – alexino2 May 14 '22 at 00:00
  • @GünterZöchbauer taking into account the input from Ben Lesh would you consider rephrasing your answer? It encourages to use BehaviourSubject as a pure value container rather than Observable. – Tomek Sep 20 '22 at 08:05
  • 1
    @Tomek I don't think it does encourage anything. I just explained a way to get what is asked in the the question. I don't like answers that say "don't do that" if there actually is a way. What the answer from Ben Lesh states is correct, but doesn't answer the question. – Günter Zöchbauer Nov 01 '22 at 15:04
225

The only way you should be getting values "out of" an Observable/Subject is with subscribe!

If you're using getValue() you're doing something imperative in declarative paradigm. It's there as an escape hatch, but 99.9% of the time you should NOT use getValue(). There are a few interesting things that getValue() will do: It will throw an error if the subject has been unsubscribed, it will prevent you from getting a value if the subject is dead because it's errored, etc. But, again, it's there as an escape hatch for rare circumstances.

There are several ways of getting the latest value from a Subject or Observable in a "Rx-y" way:

  1. Using BehaviorSubject: But actually subscribing to it. When you first subscribe to BehaviorSubject it will synchronously send the previous value it received or was initialized with.
  2. Using a ReplaySubject(N): This will cache N values and replay them to new subscribers.
  3. A.withLatestFrom(B): Use this operator to get the most recent value from observable B when observable A emits. Will give you both values in an array [a, b].
  4. A.combineLatest(B): Use this operator to get the most recent values from A and B every time either A or B emits. Will give you both values in an array.
  5. shareReplay(): Makes an Observable multicast through a ReplaySubject, but allows you to retry the observable on error. (Basically it gives you that promise-y caching behavior).
  6. publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc: Other operators that leverage BehaviorSubject and ReplaySubject. Different flavors of the same thing, they basically multicast the source observable by funneling all notifications through a subject. You need to call connect() to subscribe to the source with the subject.
forivall
  • 9,504
  • 2
  • 33
  • 58
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 37
    can you elaborte why using getValue() is a red flag? What if i need the current value of the observable only once, the moment the user clicks a button? Should I subscribe for the value and immediately unsubscribe? – Flame Jul 25 '17 at 12:49
  • 11
    It's okay sometimes. But often it's a sign that the code's author is doing something very imperative (usually with side-effects) where they shouldn't be. You can do `click$.mergeMap(() => behaviorSubject.take(1))` to solve your problem as well. – Ben Lesh Aug 10 '17 at 19:22
  • 1
    Great explanation! Actually, if you can keep Observable and use the methods, it's a much better way. In a context of typescript with Observable used everywhere but only a few one defined as BehaviourSubject, then it would be less consistant code. The methods you proposed allowed me to keep Observable types everywhere. – JLavoie Oct 26 '17 at 16:30
  • 3
    its good if you want to simply dispatch the current value (like send it to the server on click), doing a getValue() is very handy. but don't use it when chaining observable operators. – FlavorScape Apr 26 '18 at 17:51
  • @FlavorScape It's no different than doing a take(1) subscribe, although I try to avoid it (as it makes me feel uneasy somehow) – Drenai Mar 14 '19 at 23:06
  • It's fine if you're just firing an action for ngrx to decide what to do with later. – FlavorScape Mar 18 '19 at 17:52
  • 7
    I have an `AuthenticationService` which uses a `BehaviourSubject` to store the current logged in state (`boolean` `true` or `false`). It exposes an `isLoggedIn$` observable for subscribers who want to know when the state changes. It also exposes a `get isLoggedIn()` property, which returns the current logged in state by calling `getValue()` on the underlying `BehaviourSubject` - this is used by my authentication guard to check the current state. This seems like a sensible use of `getValue()` to me...? – Dan King Jul 12 '19 at 14:11
  • @DanKing from your route guard you can return an observable like so: `return isLoggedIn$.pipe(map(loggedIn => loggedIn ? true : this.router.parseUrl('/')));` – Wilt Apr 23 '20 at 14:14
  • 1
    @BenLesh doing the `click$.mergeMap(...` requires you to have the `click$` as an observable. For it you have to put a template variable onto the element, get him into the class via `ViewChild`, convert it's `nativeElement` into an Observable using `fromEvent`... See what it looks like? Right, `getElementById` and `addEventListener` :D In a modern UI framework, omg... IMHO using `BehaviorSubject.value` is the lesser evil – Nabi Isakhanov Apr 28 '22 at 15:19
21

I had similar situation where late subscribers subscribe to the Subject after its value arrived.

I found ReplaySubject which is similar to BehaviorSubject works like a charm in this case. And here is a link to better explanation: http://reactivex.io/rxjs/manual/overview.html#replaysubject

Kfir Erez
  • 3,280
  • 2
  • 18
  • 17
  • 1
    that helped me in my Angular4 app - I had to move the subscription from the component constructor to the ngOnInit() (such component is shared across routes), just leaving here in case someone has a similar issue – Luca Jul 14 '17 at 15:15
  • 1
    I had an issue with an Angular 5 app where I was using a service to get values from an api and set variables in different components. I was using subjects/observables but it wouldn't push the values after a route change. ReplaySubject was a drop in replacement for Subject and solved everything. – jafaircl Nov 29 '17 at 19:54
  • 2
    Make sure you're using ReplaySubject(1) otherwise new subscribers will get every previously emitted value in sequence - this isn't always obvious at runtime – Drenai Mar 14 '19 at 23:09
  • @Drenai as far as I understand ReplaySubject(1) behave the same as BehaviorSubject() – Kfir Erez Mar 21 '19 at 09:29
  • 4
    Not quite the same, the big difference being that ReplaySubject does not immediately emit a default value when it's subscribed to if it's `next()` function hasn't yet been invoked, whereas BehaviourSubject does. The immediate emit is very handy for applying default values to a view, when the BehaviourSubject is used as the data source observable in a service for example – Drenai Mar 23 '19 at 13:13
11
const observable = of('response')

function hasValue(value: any) {
  return value !== null && value !== undefined;
}

function getValue<T>(observable: Observable<T>): Promise<T> {
  return observable
    .pipe(
      filter(hasValue),
      first()
    )
    .toPromise();
}

const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................

You can check the full article on how to implement it from here. https://www.imkrish.com/blog/development/simple-way-get-value-from-observable

sean
  • 1,187
  • 17
  • 25
3

I encountered the same problem in child components where initially it would have to have the current value of the Subject, then subscribe to the Subject to listen to changes. I just maintain the current value in the Service so it is available for components to access, e.g. :

import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject}    from 'rxjs/Subject';

@Injectable()
export class SessionStorage extends Storage {

  isLoggedIn: boolean;

  private _isLoggedInSource = new Subject<boolean>();
  isLoggedIn = this._isLoggedInSource.asObservable();
  constructor() {
    super('session');
    this.currIsLoggedIn = false;
  }
  setIsLoggedIn(value: boolean) {
    this.setItem('_isLoggedIn', value, () => {
      this._isLoggedInSource.next(value);
    });
    this.isLoggedIn = value;
  }
}

A component that needs the current value could just then access it from the service, i.e,:

sessionStorage.isLoggedIn

Not sure if this is the right practice :)

Molp Burnbright
  • 659
  • 6
  • 5
3

A similar looking answer was downvoted. But I think I can justify what I'm suggesting here for limited cases.


While it's true that an observable doesn't have a current value, very often it will have an immediately available value. For example with redux / flux / akita stores you may request data from a central store, based on a number of observables and that value will generally be immediately available.

If this is the case then when you subscribe, the value will come back immediately.

So let's say you had a call to a service, and on completion you want to get the latest value of something from your store, that potentially might not emit:

You might try to do this (and you should as much as possible keep things 'inside pipes'):

 serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
                     .subscribe(([ serviceCallResponse, customer] => {

                        // we have serviceCallResponse and customer 
                     });

The problem with this is that it will block until the secondary observable emits a value, which potentially could be never.

I found myself recently needing to evaluate an observable only if a value was immediately available, and more importantly I needed to be able to detect if it wasn't. I ended up doing this:

 serviceCallResponse$.pipe()
                     .subscribe(serviceCallResponse => {

                        // immediately try to subscribe to get the 'available' value
                        // note: immediately unsubscribe afterward to 'cancel' if needed
                        let customer = undefined;

                        // whatever the secondary observable is
                        const secondary$ = store$.select(x => x.customer);

                        // subscribe to it, and assign to closure scope
                        sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
                        sub.unsubscribe();

                        // if there's a delay or customer isn't available the value won't have been set before we get here
                        if (customer === undefined) 
                        {
                           // handle, or ignore as needed
                           return throwError('Customer was not immediately available');
                        }
                     });

Note that for all of the above I'm using subscribe to get the value (as @Ben discusses). Not using a .value property, even if I had a BehaviorSubject.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
2

Although it may sound overkill, this is just another "possible" solution to keep Observable type and reduce boilerplate...

You could always create an extension getter to get the current value of an Observable.

To do this you would need to extend the Observable<T> interface in a global.d.ts typings declaration file. Then implement the extension getter in a observable.extension.ts file and finally include both typings and extension file to your application.

You can refer to this StackOverflow Answer to know how to include the extensions into your Angular application.

// global.d.ts
declare module 'rxjs' {
  interface Observable<T> {
    /**
     * _Extension Method_ - Returns current value of an Observable.
     * Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
     */
    value: Observable<T>;
  }
}

// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
  get <T>(this: Observable<T>): Observable<T> {
    return this.pipe(
      filter(value => value !== null && value !== undefined),
      first());
  },
});

// using the extension getter example
this.myObservable$.value
  .subscribe(value => {
    // whatever code you need...
  });
j3ff
  • 5,719
  • 8
  • 38
  • 51
2

A subscription can be created, then after taking the first emitted item, destroyed. In the example below, pipe() is a function that uses an Observable as its input and returns another Observable as its output, while not modifying the first observable.

Sample created with Angular 8.1.0 packages "rxjs": "6.5.3", "rxjs-observable": "0.0.7"

  ngOnInit() {

    ...

    // If loading with previously saved value
    if (this.controlValue) {

      // Take says once you have 1, then close the subscription
      this.selectList.pipe(take(1)).subscribe(x => {
        let opt = x.find(y => y.value === this.controlValue);
        this.updateValue(opt);
      });

    }
  }
SushiGuy
  • 1,573
  • 17
  • 20
2

There are two ways you can achieve this.

  1. BehaviorSubject has a method getValue() which you can get the value in a specific point of time.

  2. You can subscribe directly with the BehaviorSubject and you may pass the subscribed value to a class member, field or property.

I wouldn't recommend both approaches.

In the first approach, it's a convenient method you can get the value anytime, you may refer to this as the current snapshot at that point of time. Problem with this is you can introduce race conditions in your code, you may invoke this method in many different places and in different timing which is hard to debug.

The second approach is what most developers employ when they want a raw value upon subscription, you can track the subscription and when do you exactly unsubscribe to avoid further memory leak, you may use this if you're really desperate to bind it to a variable and there's no other ways to interface it.

I would recommend, looking again at your use cases, where do you use it? For example you want to determine if the user is logged in or not when you call any API, you can combine it other observables:

const data$ = apiRequestCall$().pipe(
 // Latest snapshot from BehaviorSubject.
 withLatestFrom(isLoggedIn),
 // Allow call only if logged in.
 filter(([request, loggedIn]) => loggedIn)
 // Do something else..
);

With this, you may use it directly to the UI by piping data$ | async in case of angular.

R4L_R4o
  • 161
  • 1
  • 4
0

You could store the last emitted value separately from the Observable. Then read it when needed.

let lastValue: number;

const subscription = new Service().start();
subscription
    .subscribe((data) => {
        lastValue = data;
    }
);
Slawa
  • 1,141
  • 15
  • 21
  • 4
    It's not a reactive approach to store some stuff outside the observable. Instead you should have as much data as possible flowing inside the observable streams. – ganqqwerty Dec 03 '18 at 11:49
0

The best way to do this is using Behaviur Subject, here is an example:

var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5
yaya
  • 7,675
  • 1
  • 39
  • 38
0

Another approach, If you want / can to use async await (has to be inside of an async functions) you can do this with modern Rxjs:

 async myFunction () {
     const currentValue = await firstValueFrom(
      of(0).pipe(
        withLatestFrom(this.yourObservable$),
        map((tuple) => tuple[1]),
        take(1)
      )
    );
    // do stuff with current value

 }

 

This will emit a value "Right away" because of withLatestFrom, and then will resolve the promise.

Chen Peleg
  • 953
  • 10
  • 16
0

Observables are just functions. They return a stream of values, but in order to see those values, you need to subscribe to them. The most basic way is to call subscribe() method on the observable and pass in an observer or a next() callback as an argument.

Alternatively, you can use a Subscriber which is an implementation of the Observer but provdes you with added capabilities such as unsubscribe.

import { Subscription } from 'rxjs';

export class SessionStorage extends Storage {
  private _isLoggedInSource = new Subject<boolean>();
  privat _subs = new Subscription();
  isLoggedIn$ = this._isLoggedInSource.asObservable();
  isLoggedIn = false;
  constructor() {
    super('session');
    this._subs.add(this.isLoggedIn$.subscribe((value) => this.isLoggedIn = v))
  }
  setIsLoggedIn(value: boolean) {
    this.setItem('_isLoggedIn', value, () => {
      this._isLoggedInSource.next(value);
    });
  }
}

Then in another component, you can import SessionStorage and access the value of isLoggedIn.

NOTE: I would use the new Observable() constructor to encapsulate the data of _isLoggedInSource within an observable.

bytrangle
  • 979
  • 11
  • 9