จะรับค่าปัจจุบันของ 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 ณ เวลาใดเวลาหนึ่ง ฉันจะทำสิ่งนี้ได้อย่างไร

คำตอบ:


341

A SubjectหรือObservableไม่มีค่าปัจจุบัน เมื่อค่าถูกปล่อยออกมามันจะถูกส่งไปยังสมาชิกและObservableจะทำกับมัน

หากคุณต้องการมีค่าปัจจุบันให้ใช้ค่าBehaviorSubjectที่ออกแบบมาเพื่อจุดประสงค์นั้น BehaviorSubjectเก็บค่าที่ปล่อยออกมาล่าสุดและส่งไปยังสมาชิกใหม่ทันที

นอกจากนี้ยังมีวิธีgetValue()การรับค่าปัจจุบัน


1
สวัสดีมันไม่ดีของฉันพวกเขาเป็นสิ่งเดียวกันใน rxjs 5 ลิงค์ซอร์สโค้ดด้านบนได้อ้างถึง rxjs4
รหัสของ schrodinger

84
หมายเหตุสำคัญจากผู้เขียน RxJS 5: การใช้getValue()เป็นธงสีแดงขนาดใหญ่ที่คุณกำลังทำอะไรผิด มันมีช่องสำหรับหลบหนี โดยทั่วไปทุกสิ่งที่คุณทำกับ RxJS ควรเป็นการประกาศ getValue()มีความจำเป็น หากคุณกำลังใช้getValue()งานมีโอกาส 99.9% ที่คุณทำผิดหรือแปลก
เบ็นเลช

1
@ BenLesh ถ้าฉันมีBehaviorSubject<boolean>(false)และชอบที่จะสลับมันได้หรือไม่
บ๊อบ

3
@BenLesh getValue () มีประโยชน์อย่างมากสำหรับการพูดการดำเนินการในทันทีเช่น onClick-> dispatchToServer (x.getValue ()) แต่อย่าใช้มันในเครือข่ายที่สังเกตได้
FlavourScape

2
@ IngoBürkวิธีเดียวที่ฉันสามารถปรับคำแนะนำของคุณคือถ้าคุณไม่ทราบว่าfoo$เป็นBehaviorSubject(คือมันถูกกำหนดให้เป็นObservableและบางทีมันอาจจะร้อนหรือเย็นของจากแหล่งที่รอการตัดบัญชี) อย่างไรก็ตามเนื่องจากคุณยังคงใช้.nextด้วย$fooตัวเองซึ่งหมายความว่าการใช้งานของคุณขึ้นอยู่กับการใช้งานBehaviorSubjectดังนั้นจึงไม่มีเหตุผลที่ไม่เพียง แต่.valueจะใช้เพื่อให้ได้มูลค่าปัจจุบันตั้งแต่แรก
Simon_Weaver

145

วิธีเดียวที่คุณควร รับค่า "นอก" Observable / Subject คือการสมัครสมาชิก!

หากคุณกำลังใช้getValue()คุณกำลังทำบางสิ่งที่จำเป็นในกระบวนทัศน์ที่เปิดเผย มันมีเป็นหนีออกจากไข่ แต่99.9% getValue()ของเวลาที่คุณไม่ควรใช้ มีสิ่งที่น่าสนใจสองสามอย่างที่getValue()จะทำ: มันจะโยนข้อผิดพลาดหากหัวเรื่องถูกยกเลิกการเป็นสมาชิกมันจะป้องกันไม่ให้คุณได้รับค่าถ้าหัวเรื่องนั้นตายเพราะมีข้อผิดพลาด ฯลฯ แต่อีกครั้งมันเป็นการหลบหนี ฟักเป็นตัวหายาก

มีหลายวิธีในการรับค่าล่าสุดจากหัวเรื่องหรือสังเกตได้ด้วยวิธี "Rx-y":

  1. ใช้BehaviorSubject: แต่จริงสมัครรับมัน เมื่อคุณสมัครเป็นสมาชิกครั้งแรกBehaviorSubjectมันจะส่งค่าก่อนหน้านี้พร้อมกันที่ได้รับหรือเริ่มต้นด้วย
  2. การใช้ReplaySubject(N): สิ่งนี้จะเก็บNค่าและเล่นซ้ำกับสมาชิกใหม่
  3. A.withLatestFrom(B): ใช้โอเปอเรเตอร์นี้เพื่อรับค่าล่าสุดจากสิ่งที่สังเกตได้Bเมื่อAปล่อยออกมาซึ่งสามารถสังเกตได้ [a, b]จะทำให้คุณทั้งสองค่าในอาร์เรย์
  4. A.combineLatest(B): ใช้ดำเนินการนี้จะได้รับค่าล่าสุดจากAและBทุกครั้งที่ทั้งสองAหรือBส่งเสียง จะให้คุณทั้งสองค่าในอาร์เรย์
  5. shareReplay(): สร้างมัลติคาสต์ที่สังเกตได้ผ่านทาง a ReplaySubjectแต่ให้คุณลองลองใหม่อีกครั้งด้วยข้อผิดพลาด (โดยทั่วไปจะให้พฤติกรรมแคชที่สัญญากับคุณ)
  6. publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject)ฯลฯ : ผู้ประกอบการอื่น ๆ ที่ใช้ประโยชน์และBehaviorSubject ReplaySubjectรสชาติที่แตกต่างกันของสิ่งเดียวกันโดยทั่วไปแล้วพวกเขาจะส่งสัญญาณแบบหลายผู้รับที่สามารถสังเกตได้โดยการส่งการแจ้งเตือนทั้งหมดผ่านทางวัตถุ คุณต้องโทรconnect()เพื่อสมัครสมาชิกแหล่งที่มากับเรื่อง

19
คุณสามารถอธิบายได้ว่าเหตุใดการใช้ getValue () เป็นธงสีแดง ถ้าฉันต้องการมูลค่าปัจจุบันของสิ่งที่สังเกตได้เพียงครั้งเดียวช่วงเวลาที่ผู้ใช้คลิกปุ่ม? ฉันควรสมัครรับค่าและยกเลิกการสมัครทันทีหรือไม่
Flame

5
บางครั้งก็โอเค แต่บ่อยครั้งที่มันเป็นสัญญาณว่าผู้เขียนโค้ดกำลังทำสิ่งที่จำเป็นมาก (โดยปกติจะมีผลข้างเคียง) ที่ไม่ควรเป็น คุณสามารถclick$.mergeMap(() => behaviorSubject.take(1))แก้ไขปัญหาของคุณได้เช่นกัน
เบ็นเลช

1
คำอธิบายที่ดี! ที่จริงแล้วถ้าคุณสามารถสังเกตได้และใช้วิธีการต่อไปมันเป็นวิธีที่ดีกว่ามาก ในบริบทของ typescript ที่มี Observable ใช้อยู่ทุกที่ แต่มีเพียงไม่กี่ตัวที่นิยามเป็น BehaviourSubject ดังนั้นมันจะเป็นรหัสที่สอดคล้องกันน้อยลง วิธีการที่คุณเสนอให้ฉันเก็บประเภทการสังเกตได้ทุกที่
JLavoie

2
มันดีถ้าคุณต้องการเพียงแค่ส่งค่าปัจจุบัน (เช่นส่งไปยังเซิร์ฟเวอร์เมื่อคลิก) การทำ getValue () นั้นมีประโยชน์มาก แต่อย่าใช้มันเมื่อผูกมัดตัวดำเนินการที่สังเกตได้
FlavourScape

1
ฉันมีAuthenticationServiceที่ใช้BehaviourSubjectเพื่อเก็บสถานะการเข้าสู่ระบบในปัจจุบัน ( boolean trueหรือfalse) มันทำให้ผู้isLoggedIn$ติดตามที่ต้องการทราบเมื่อรัฐเปลี่ยนแปลง มันยังแสดงget isLoggedIn()คุณสมบัติซึ่งส่งคืนสถานะการเข้าสู่ระบบปัจจุบันโดยการเรียกgetValue()ใช้งานต้นแบบBehaviourSubject- นี่คือการใช้งานโดยเจ้าหน้าที่ตรวจสอบของฉันเพื่อตรวจสอบสถานะปัจจุบัน ดูเหมือนว่าgetValue()ฉันจะใช้มันอย่างมีเหตุผล... ?
Dan King

12

ฉันมีสถานการณ์ที่คล้ายกันซึ่งผู้สมัครสมาชิกล่าช้าได้สมัครรับเรื่องหลังจากที่มูลค่ามาถึง

ฉันพบReplaySubjectซึ่งคล้ายกับ BehaviorSubject ใช้งานได้อย่างมีเสน่ห์ในกรณีนี้ และนี่คือลิงก์สำหรับคำอธิบายที่ดีกว่า: http://reactivex.io/rxjs/manual/overview.html#replaysubject


1
ที่ช่วยฉันในแอป Angular4 ของฉัน - ฉันต้องย้ายการสมัครสมาชิกจากตัวสร้างส่วนประกอบไปยัง ngOnInit () (องค์ประกอบดังกล่าวแชร์ข้ามเส้นทาง) เพิ่งออกจากที่นี่ในกรณีที่มีคนที่มีปัญหาคล้ายกัน
Luca

1
ฉันมีปัญหากับแอป Angular 5 ที่ฉันใช้บริการเพื่อรับค่าจาก api และตั้งค่าตัวแปรในส่วนประกอบต่าง ๆ ฉันใช้วิชา / สิ่งที่สังเกตได้ แต่มันจะไม่ผลักดันค่าหลังจากเปลี่ยนเส้นทาง ReplaySubject เป็นการแทนที่ของหัวเรื่องและแก้ไขทุกอย่าง
jafaircl

1
ตรวจสอบให้แน่ใจว่าคุณกำลังใช้ ReplaySubject (1) ไม่เช่นนั้นสมาชิกใหม่จะได้รับค่าที่ปล่อยออกมาก่อนหน้านี้ตามลำดับ - นี่ไม่ชัดเจนเสมอที่รันไทม์
Drenai

@Drenai เท่าที่ฉันเข้าใจ ReplaySubject (1) ประพฤติเช่นเดียวกับ BehaviorSubject ()
Kfir Erez

ไม่เหมือนกันความแตกต่างใหญ่ที่ ReplaySubject ไม่ปล่อยค่าเริ่มต้นทันทีเมื่อสมัครสมาชิกหากnext()ยังไม่ได้เรียกใช้ฟังก์ชันในขณะที่ BehaviourSubject ทำ การปล่อยทันทีนั้นมีประโยชน์มากสำหรับการใช้ค่าเริ่มต้นกับมุมมองเมื่อ BehaviourSubject ถูกใช้เป็นแหล่งข้อมูลที่สังเกตได้ในบริการตัวอย่างเช่น
Drenai

5
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

ฉันพบปัญหาเดียวกันในองค์ประกอบลูกซึ่งในขั้นต้นจะต้องมีค่าปัจจุบันของหัวเรื่องจากนั้นสมัครสมาชิกเพื่อรับฟังการเปลี่ยนแปลง ฉันเพิ่งรักษามูลค่าปัจจุบันในบริการเพื่อให้สามารถเข้าถึงส่วนประกอบได้เช่น:

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
หากคุณต้องการคุณค่าของสิ่งที่สังเกตได้ในมุมมององค์ประกอบของคุณคุณสามารถใช้asyncไพพ์ได้
Ingo Bürk

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 อธิบาย) ไม่ได้ใช้คุณสมบัติแม้ว่าฉันมี.valueBehaviorSubject


1
Btw นี้ใช้งานได้เนื่องจากโดยค่าเริ่มต้น 'กำหนดการ' ใช้เธรดปัจจุบัน
Simon_Weaver

1

แม้ว่ามันอาจฟังดูเกินจริง แต่นี่เป็นอีกวิธีการแก้ปัญหา "ที่เป็นไปได้" เพื่อให้สังเกตได้ประเภทที่และลดความร้อน ...

คุณสามารถสร้างส่วนขยายทะเยอทะยานเพื่อรับค่าปัจจุบันของ Observable

ในการทำเช่นนี้คุณจะต้องขยายObservable<T>ส่วนต่อประสานในglobal.d.tsไฟล์ประกาศการพิมพ์ จากนั้นปรับใช้ตัวขยายส่วนขยายในobservable.extension.tsไฟล์และในที่สุดก็รวมทั้งการพิมพ์และไฟล์ส่วนขยายเข้ากับแอปพลิเคชันของคุณ

คุณสามารถอ้างถึงStackOverflow Answerเพื่อทราบวิธีรวมส่วนขยายในแอปพลิเคชันเชิงมุมของคุณ

// 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 อื่นเป็นเอาต์พุตในขณะที่ไม่แก้ไขสิ่งที่สังเกตได้ครั้งแรก เชิงมุม 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);
      });

    }
  }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.