从Observable获取当前值而无需订阅(只需要一次值)

时间:2016-05-20 06:01:24

标签: angular rxjs observable

标题说明了一切。如何在不订阅的情况下从Observable获取当前值?我只想要当前值一次,而不是新值,因为它们正在进入......

5 个答案:

答案 0 :(得分:25)

您需要使用BehaviorSubject

  
      
  • BehaviorSubject类似于ReplaySubject,只是它只记住上一个发布。
  •   
  • BehaviorSubject还要求您为其提供默认值T.这意味着所有订阅者都将立即收到一个值   (除非它已经完成)。
  •   

它将为您提供Observable发布的最新值。

BehaviorSubject提供名为getter的{​​{1}}属性,以获取通过它的最新值。

PLUNKER DEMO

  • 在此示例中,值'a'将写入控制台:

value

用法:

  //Declare a Subject, you'll need to provide a default value.
  var subject: BehaviorSubject<string> = new BehaviorSubject("a");

答案 1 :(得分:2)

快速解答:

  

...我只希望当前值一次而不是新值,因为它们要传入...

您仍将使用subscribe,但使用pipe(take(1))时,它将为您提供一个单一值。

例如myObs$.pipe(take(1)).subscribe(value => alert(value));

另请参阅:first(), take(1) or single()

之间的比较

更长的答案:

一般规则是,您只能从subscribe()的可观察值中获取一个值

(如果使用Angular,则为异步管道)

BehaviorSubject肯定有它的位置,当我开始使用RxJS时,我经常会做bs.value()来获取价值。随着您的RxJS流在整个应用程序中传播(这就是您想要的!),这样做将变得越来越困难。通常,您实际上经常会看到.asObservable()用于“隐藏”基础类型以防止使用.value()的人-起初这似乎很刻薄,但是您将开始欣赏为什么它随着时间的推移而完成。另外,您迟早会需要一个不是 BehaviorSubject的值,并且没有办法实现这一点。

但是回到原始问题。尤其是如果您不想使用BehaviorSubject来“作弊”。

更好的方法是始终使用subscribe来获取价值。

obs$.pipe(take(1)).subscribe(value => { ....... })

OR

obs$.pipe(first()).subscribe(value => { ....... })

如果流已经完成,则这两个first()之间的差将错误,并且如果流已经完成或没有流,take(1)将不会发出任何可观察到的东西立即可用的值。

注意:即使您正在使用BehaviorSubject,这也被认为是更好的做法。

但是,如果您尝试上面的代码,则可观察对象的“值”将被“卡住”在订阅函数的闭包内,您很可能在当前作用域中需要它。解决这一问题的一种方法是:

const obsValue = undefined;
const sub = obs$.pipe(take(1)).subscribe(value => obsValue = value);
sub.unsubscribe();

// we will only have a value here if it was IMMEDIATELY available
alert(obsValue);

重要的是请注意,上述订阅调用不会等待获取值。如果没有立即可用的订阅功能,则永远不会调用该订阅功能,因此我故意将取消订阅的调用放在此处,以防止其“稍后出现”。

因此,这不仅显得非常笨拙-不适用于无法立即使用的内容(例如来自http调用的结果值),而且实际上可以与行为主题一起使用(或更重要的是, (在上游,并且被称为BehaviorSubject **或采用两个combineLatest值的BehaviorSubject)。而且绝对不要去做(obs$ as BehaviorSubject)-啊!

前面的示例通常仍然被认为是不好的做法-情况一团糟。仅当我想查看一个值是否立即可用并能够检测出它是否可用时,我才使用以前的代码样式。

最佳方法

如果您可以将所有内容保持在尽可能长的可观察范围内,那么您的境况要好得多;只有在您绝对需要该值时才订阅它,而不是尝试将一个值“提取”到一个包含作用域的范围内在上面。

例如假设您的动物园开放,我们想报告我们的动物。您可能会认为您想要这样的zooOpen$的“提取”值:

坏方法

zooOpen$: Observable<boolean> = of(true);    // is the zoo open today?
bear$: Observable<string> = of('Beary');
lion$: Observable<string> = of('Liony');

runZooReport() {

   // we want to know if zoo is open!
   // this uses the approach described above

   const zooOpen: boolean = undefined;
   const sub = this.zooOpen$.subscribe(open => zooOpen = open);
   sub.unsubscribe();

   // 'zooOpen' is just a regular boolean now
   if (zooOpen) 
   {
      // now take the animals, combine them and subscribe to it
      combineLatest(this.bear$, this.lion$).subscribe(([bear, lion]) => {

          alert('Welcome to the zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion); 
      });
   }
   else 
   {
      alert('Sorry zoo is closed today!');
   }
}

那为什么这么糟糕

  • 如果zooOpen$来自网络服务怎么办?前面的示例将如何工作?实际上,您的服务器有多快并不重要-如果zooOpen$是可观察到的http,那么您将永远不会获得上述代码的值!
  • 如果要在此功能“外部”使用此报告怎么办。现在,您已将alert锁定为此方法。如果您必须在其他地方使用该报告,则必须重复此操作!

好方法

而不是尝试访问函数中的值,而是考虑创建一个新的Observable甚至不订阅它的函数!

它返回一个可以在“外部”使用的新​​可观察对象。

通过将所有内容保持为可观察对象并使用switchMap进行决策,您可以创建新的可观察对象,而这些新观察对象本身也可以成为其他可观察对象的来源。

getZooReport() {

  return this.zooOpen$.pipe(switchMap(zooOpen => {

     if (zooOpen) {

         return combineLatest(this.bear$, this.lion$).pipe(map(([bear, lion] => {

                 // this is inside 'map' so return a regular string
                 return "Welcome to the zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion;
              }
          );
      }
      else {

         // this is inside 'switchMap' so *must* return an observable
         return of('Sorry the zoo is closed today!');
      }

   });
 }

上面的创建了一个新的可观察对象,因此我们可以在其他地方运行它,并根据需要通过管道进行更多的操作。

 const zooReport$ = this.getZooReport();
 zooReport$.pipe(take(1)).subscribe(report => {
    alert('Todays report: ' + report);
 });

 // or take it and put it into a new pipe
 const zooReportUpperCase$ = zooReport$.pipe(map(report => report.toUpperCase()));

请注意以下几点:

  • 直到绝对需要时我才订阅-在这种情况下,该功能不在
  • “行驶”可观测值是zooOpen$,它使用switchMap来“切换”到另一个可观测值,该观测值最终是从getZooReport()返回的那个。
  • 如果zooOpen$不断变化,它的工作方式将取消所有内容并在第一个switchMap中重新开始。阅读有关switchMap的更多信息。
  • 注意:switchMap中的代码必须返回一个新的observable。您可以使用of('hello')快速创建一个-或返回另一个可观察到的变量,例如combineLatest
  • 同样,map必须只返回常规字符串。

我很快就开始刻意不要订阅,直到我拥有,我突然开始编写更具生产力,灵活性,简洁性和可维护性的代码。

另一个最后的注意事项:如果将这种方法与Angular结合使用,则可以使用subscribe管道来获得上述Zoo报告而没有单个| async。这是实践中“直到您必须先订阅”的一个很好的例子。

// in your angular .ts file for a component
const zooReport$ = this.getZooReport();

并在您的模板中:

<pre> {{ zooReport$ | async }} </pre>

另请参阅我的答案:

https://stackoverflow.com/a/54209999/16940

上面也没有提到以避免混淆:

  • tap()有时可能对“从可观察的事物中获取价值”很有用。如果您不熟悉该运算符,请仔细阅读。 RxJS使用“管道”,而tap()运算符是一种“进入管道”以查看其中的内容的方式。

答案 2 :(得分:1)

常量值=等待this.observableMethod()。toPromise();

答案 3 :(得分:0)

不确定这是否是您想要的。可能将该行为主题写入服务中。将其声明为私有并仅公开您设置的值。像这样

 @Injectable({
   providedIn: 'root'
 })
  export class ConfigService {
    constructor(private bname:BehaviorSubject<String>){
       this.bname = new BehaviorSubject<String>("currentvalue");
    }

    getAsObservable(){
       this.bname.asObservable();
    }
 }

通过这种方式,外部用户仅具有订阅behaviorSubject的权限,您就可以在服务中设置所需的值。

答案 4 :(得分:0)

使用Observable构造函数创建任何类型的可观察流。构造函数将可观察者的subscription()方法执行时运行的订阅者函数作为参数。订阅者函数接收一个Observer对象,并且可以将值发布到观察者的next()方法。尝试此

@Component({
  selector: 'async-observable-pipe',
  template: '<div><code>observable|async</code>: Time: {{ time | async }} . 
</div>'
})
export class AsyncObservablePipeComponent {
  time = new Observable<string>((observer: Observer<string>) => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}