如何获取RxJS Subject或Observable的当前值?


206

我有一个Angular 2服务:

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);
    });
  }
}

一切正常。但是我还有另一个不需要订阅的组件,它只需要在某个时间点获取isLoggedIn的当前值即可。我怎样才能做到这一点?

Answers:


341

一个SubjectObservable没有的电流值。发出值时,将其传递给订户并Observable使用它完成。

如果要使用当前值,请使用BehaviorSubject专门用于该目的的值。BehaviorSubject保留最后发出的值,并立即将其发送给新订户。

它还具有一种getValue()获取当前值的方法。


1
嗨,这很糟糕,现在在rxjs 5中它们是同一回事。上面的源代码链接引用了rxjs4。
薛定inger的代码

84
RxJS 5作者的重要说明:使用getValue()是一个巨大的危险信号,说明您做错了什么。它在那里作为逃生舱口。通常,使用RxJS进行的所有操作都应为声明性的。getValue()势在必行。如果您使用getValue(),则有99.9%的机会犯错或怪异。
Ben Lesh

1
@BenLesh如果我有一个BehaviorSubject<boolean>(false)并且喜欢切换它怎么办?
bob

3
@BenLesh getValue()对于执行瞬时操作非常有用,例如onClick-> dispatchToServer(x.getValue()),但不要在可观察的链中使用它。
FlavorScape

2
@IngoBürk我能证明您的建议的唯一方法是,如果您不知道这foo$是一个原因BehaviorSubject(即,它被定义为Observable,也许来自延迟来源是很热还是很冷)。但是,因为你接下来要使用.next$foo本身,这意味着你的实现依赖于它一个BehaviorSubject如此有不只是使用没有任何理由.value让摆在首位的电流值。
Simon_Weaver

144

你的唯一方法 应该 从“可观察/主题”之外获取值是订阅!

如果您使用的getValue()是声明式范例,则必须执行某些命令。它可以作为逃生舱口,但是99.9%的时间不应该使用getValue()有一些有趣的事情getValue()可以做:如果主题未订阅,它将引发错误;如果主题因错误而死亡,它将阻止您获取值。在极少数情况下孵化。

有几种以“ Rx-y”方式从Subject或Observable获取最新值的方法:

  1. 使用BehaviorSubject:但实际上是订阅它。首次订阅时BehaviorSubject,它将同步发送接收到的或初始化时使用的先前值。
  2. 使用ReplaySubject(N):这将缓存N值并将其重播给新订户。
  3. A.withLatestFrom(B):使用此运算符可以B在可观察到的A发射时从可观察的获取最新值。将在数组中同时提供两个值[a, b]
  4. A.combineLatest(B):使用该运营商从中获取最新的值AB每一次无论是AB发出。将在数组中同时提供两个值。
  5. shareReplay():通过进行可观察的多播ReplaySubject,但允许您在发生错误时重试可观察的。(基本上,它为您提供了承诺的缓存行为)。
  6. publishReplay()publishBehavior(initialValue)multicast(subject: BehaviorSubject | ReplaySubject),等:其它运营商,充分利用BehaviorSubjectReplaySubject。相同事物的口味不同,它们基本上通过将所有通知集中到某个主题来多播可观察的源。您需要致电connect()以订阅带有该主题的来源。

19
你能解释为什么使用getValue()是一个红旗吗?如果我仅在用户单击按钮时需要一次可观察值的当前值,该怎么办?我应该订阅该值并立即退订吗?
Flame

5
有时候没关系。但是,通常这表明该代码的作者正在做某些当务之急(通常带有副作用),而不应该这样做。您也可以click$.mergeMap(() => behaviorSubject.take(1))解决您的问题。
Ben Lesh

1
很好的解释!实际上,如果您可以保持Observable并使用这些方法,则这是一种更好的方法。在一个随处可见的Typescript带有Observable的打字稿上下文中,只有很少的一个被定义为BehaviourSubject,那么它的代码一致性就会降低。您提出的方法使我可以在任何地方保留Observable类型。
JLavoie

2
如果您只想分派当前值(例如在单击时将其发送到服务器),则非常有用,执行getValue()非常方便。但是在链接可观察的运算符时不要使用它。
FlavorScape

1
我有一个AuthenticationService使用BehaviourSubject来存储当前登录状态(boolean truefalse)的。isLoggedIn$对于想要知道状态何时更改的订户,它提供了一个可观察的对象。它还公开了一个get isLoggedIn()属性,该属性通过调用getValue()基础结构来返回当前的登录状态BehaviourSubject-我的身份验证保护人员使用该属性来检查当前状态。getValue()对我来说这似乎是明智的使用...?
丹·金

11

我也遇到过类似的情况,即后期订阅者在其价值到达后就订阅了该主题。

我发现ReplaySubject与BehaviorSubject相似,在这种情况下,它就像一个饰物。这是更好解释的链接:http : //reactivex.io/rxjs/manual/overview.html#replaysubject


1
这对我的Angular4应用程序有帮助-我不得不将订阅从组件构造函数移动到ngOnInit()(此类组件在路由之间共享),如果有人遇到类似问题,请留在这里
Luca

1
我在使用Angular 5应用程序时遇到问题,该应用程序正在使用服务从api获取值并在不同组件中设置变量。我使用的是主题/可观察对象,但更改路线后不会推送值。ReplaySubject是代替Subject的一滴,解决了所有问题。
jafaircl '17

1
确保您使用的是ReplaySubject(1),否则新订阅者将按顺序获取每个先前发出的值-这在运行时并不总是很明显
Drenai

据我了解,@ Drenai ReplaySubject(1)的行为与BehaviorSubject()相同
Kfir Erez

不太一样,最大的区别在于,如果ReplaySubject的next()功能尚未被调用,则ReplaySubject在订阅时不会立即发出默认值,而BehaviourSubject却没有。当BehaviourSubject用作服务中可观察到的数据源时,例如
Drenai '19

4
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
// .................
// .................
// .................

您可以从此处查看有关如何实现它的完整文章。 https://www.imkrish.com/how-to-get-current-value-of-observable-in-a-clean-way/


3

我在子组件中遇到了相同的问题,最初它必须具有Subject的当前值,然后订阅Subject才能侦听更改。我只是在服务中维护当前值,以便组件可以访问它,例如:

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;
  }
}

需要当前值的组件可以从服务中访问它,即:

sessionStorage.isLoggedIn

不知道这是否是正确的做法:)


2
如果需要在组件视图中使用observable的值,则可以仅使用async管道。
IngoBürk17年

2

类似的寻找答案downvoted。但是我认为我可以证明我在有限情况下的建议。


确实,可观察对象没有当前值,但通常情况下它将具有立即可用的值。例如,对于redux / flux / akita存储库,您可以根据多个可观察对象向中央存储库请求数据,并且该值通常可以立即获得。

如果是这种情况,那么当您输入时subscribe,该值将立即返回。

假设您有一个服务调用,并且在完成后希望从商店中获取某商品的最新价值,这可能不会发出

您可以尝试执行此操作(并且应尽可能将内容保留在“管道内”):

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

                        // we have serviceCallResponse and customer 
                     });

问题在于它将阻塞直到辅助可观察对象发出一个值,该值可能永远不会出现。

我发现自己最近仅在值立即可用时才需要评估一个可观察,更重要的是,我需要能够检测它是否不存在。我最终这样做:

 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');
                        }
                     });

请注意,对于上述所有内容,我都在使用subscribe以获得值(如@Ben讨论的那样)。.value即使使用,也不会使用属性BehaviorSubject


1
顺便说一句,这是可行的,因为默认情况下“计划”使用当前线程。
Simon_Weaver

1

尽管这听起来有些矫kill过正,但这仅仅是保持Observable类型并减少样板的另一种“可能”解决方案。

您总是可以创建扩展获取器来获取Observable的当前值。

为此,您需要Observable<T>在类型global.d.ts声明文件中扩展接口。然后在文件中实现扩展名getterobservable.extension.ts最后将类型和扩展名文件都包括到您的应用程序中。

您可以参考此StackOverflow答案以了解如何将扩展包含在Angular应用程序中。

// 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...
  });

0

您可以将上一个发出的值与Observable分开存储。然后在需要时阅读。

let lastValue: number;

const subscription = new Service().start();
subscription
    .subscribe((data) => {
        lastValue = data;
    }
);

1
在可观察范围之外存储一些东西并不是一种被动的方法。取而代之的是,您应该在可观察的流内部流尽可能多的数据。
ganqqwerty

0

最好的方法是使用Behaviur Subject,这是一个示例:

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

0

可以创建订阅,并在销毁第一个发出的项之后将其销毁。管道是使用Observable作为其输入并返回另一个Observable作为输出的函数,而不修改第一个Observable。角8.1.0。封装:"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);
      });

    }
  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.