Bagaimana cara mendapatkan nilai saat ini dari Subjek atau Observasi RxJS?

207

Saya memiliki layanan 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);
    });
  }
}

Semuanya bekerja dengan baik. Tapi saya punya komponen lain yang tidak perlu berlangganan, hanya perlu mendapatkan nilai isLoggedIn saat ini pada titik waktu tertentu. Bagaimana saya bisa melakukan ini?

Baconbeastnz
sumber

Jawaban:

341

A Subjectatau Observabletidak memiliki nilai saat ini. Ketika suatu nilai dipancarkan, nilai itu diteruskan ke pelanggan dan Observableselesai dengan itu.

Jika Anda ingin memiliki nilai saat ini, gunakan BehaviorSubjectyang dirancang untuk tujuan tersebut. BehaviorSubjectmenyimpan nilai yang dipancarkan terakhir dan segera memancarkannya ke pelanggan baru.

Ini juga memiliki metode getValue()untuk mendapatkan nilai saat ini.

Günter Zöchbauer
sumber
1
Hai, ini buruk saya, mereka adalah hal yang sama sekarang di rxjs 5. Tautan kode sumber di atas mengacu pada rxjs4.
kode
84
Catatan penting dari penulis RxJS 5: Menggunakan getValue()adalah bendera merah BESAR Anda melakukan sesuatu yang salah. Itu ada di sana sebagai pintu keluar. Secara umum semua yang Anda lakukan dengan RxJS harus bersifat deklaratif. getValue()sangat penting. Jika Anda menggunakan getValue(), ada kemungkinan 99,9% Anda melakukan sesuatu yang salah atau aneh.
Ben Lesh
1
@ BenLesh bagaimana jika saya memiliki BehaviorSubject<boolean>(false)dan suka mengaktifkannya?
bob
3
@BenLesh getValue () sangat berguna untuk mengatakan, melakukan tindakan instan, seperti onClick-> dispatchToServer (x.getValue ()) tetapi tidak menggunakannya dalam rantai yang dapat diamati.
FlavourScape
2
@ IngoBürk Satu-satunya cara saya dapat membenarkan saran Anda adalah jika Anda tidak tahu itu foo$adalah BehaviorSubject(yaitu didefinisikan sebagai Observabledan mungkin panas atau dingin dari sumber yang ditangguhkan). Namun karena Anda kemudian pergi ke penggunaan .nextpada $foodirinya sendiri itu berarti pelaksanaan Anda bergantung pada hal itu menjadi suatu BehaviorSubjectsehingga tidak ada pembenaran untuk tidak hanya menggunakan .valueuntuk mendapatkan nilai saat ini di tempat pertama.
Simon_Weaver
145

Satu-satunya cara Anda harus mendapatkan nilai "keluar dari" Subjek yang Dapat Diamati / adalah dengan berlangganan!

Jika Anda menggunakan getValue()Anda melakukan sesuatu yang penting dalam paradigma deklaratif. Itu ada di sana sebagai pintu keluar darurat, tetapi 99,9% dari waktu Anda TIDAK boleh menggunakan getValue(). Ada beberapa hal menarik yang getValue()akan dilakukan: Ini akan melempar kesalahan jika subjek telah berhenti berlangganan, itu akan mencegah Anda dari mendapatkan nilai jika subjek mati karena itu salah, dll. Tapi, sekali lagi, itu ada sebagai pelarian menetas untuk keadaan langka.

Ada beberapa cara untuk mendapatkan nilai terbaru dari Subjek atau Dapat Diamati dengan cara "Rx-y":

  1. Menggunakan BehaviorSubject: Tapi sebenarnya berlangganan itu . Ketika Anda pertama kali berlangganan BehaviorSubjectakan secara sinkron mengirim nilai sebelumnya diterima atau diinisialisasi dengan.
  2. Menggunakan ReplaySubject(N): Ini akan men-cache Nnilai dan memutar ulang ke pelanggan baru.
  3. A.withLatestFrom(B): Gunakan operator ini untuk mendapatkan nilai terbaru dari yang dapat diamati Bsaat Adipancarkan. Akan memberi Anda kedua nilai dalam array [a, b].
  4. A.combineLatest(B): Gunakan operator ini untuk mendapatkan nilai terbaru dari Adan Bsetiap kali salah satu Aatau Bmemancarkan. Akan memberi Anda kedua nilai dalam array.
  5. shareReplay(): Membuat multicast yang Dapat Diobservasi melalui a ReplaySubject, tetapi memungkinkan Anda untuk mencoba lagi kesalahan yang dapat diamati. (Pada dasarnya itu memberi Anda perilaku caching janji-y).
  6. publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), Dll: operator lain bahwa leverage BehaviorSubjectdan ReplaySubject. Rasa berbeda dari hal yang sama, mereka pada dasarnya multicast sumber yang dapat diamati dengan menyalurkan semua pemberitahuan melalui subjek. Anda perlu menelepon connect()untuk berlangganan ke sumber dengan subjek.
Ben Lesh
sumber
19
dapatkah Anda menjelaskan mengapa menggunakan getValue () adalah tanda bahaya? Bagaimana jika saya membutuhkan nilai saat ini yang dapat diamati hanya sekali, saat pengguna mengklik tombol? Haruskah saya berlangganan nilainya dan segera berhenti berlangganan?
Nyala
5
Kadang tidak apa-apa. Namun seringkali itu pertanda bahwa pembuat kode melakukan sesuatu yang sangat penting (biasanya dengan efek samping) di mana mereka seharusnya tidak. Anda dapat melakukannya click$.mergeMap(() => behaviorSubject.take(1))untuk menyelesaikan masalah Anda juga.
Ben Lesh
1
Penjelasan hebat! Sebenarnya, jika Anda bisa tetap dapat diamati dan menggunakan metode, itu cara yang jauh lebih baik. Dalam konteks naskah dengan Observable digunakan di mana-mana tetapi hanya beberapa yang didefinisikan sebagai BehaviourSubject, maka itu akan menjadi kode yang kurang konsisten. Metode yang Anda usulkan memungkinkan saya menyimpan tipe yang Dapat Diamati di mana-mana.
JLavoie
2
ada baiknya jika Anda hanya ingin mengirim nilai saat ini (seperti mengirimnya ke server saat klik), melakukan getValue () sangat berguna. tapi jangan menggunakannya saat merantai operator yang bisa diamati.
FlavourScape
1
Saya memiliki AuthenticationServiceyang menggunakan BehaviourSubjectuntuk menyimpan status login saat ini ( boolean trueatau false). Ini memperlihatkan sebuah isLoggedIn$diamati untuk pelanggan yang ingin tahu kapan negara berubah. Itu juga memperlihatkan get isLoggedIn()properti, yang mengembalikan keadaan login saat ini dengan memanggil getValue()yang mendasarinya BehaviourSubject- ini digunakan oleh penjaga otentikasi saya untuk memeriksa keadaan saat ini. Sepertinya ini masuk akal getValue()bagiku ...?
Dan King
13

Saya memiliki situasi yang sama di mana pelanggan yang terlambat berlangganan ke Subjek setelah nilainya tiba.

Saya menemukan ReplaySubject yang mirip dengan BehaviorSubject berfungsi seperti pesona dalam kasus ini. Dan di sini ada tautan ke penjelasan yang lebih baik: http://reactivex.io/rxjs/manual/overview.html#replaysubject

Kfir Erez
sumber
1
yang membantu saya dalam aplikasi Angular4 saya - Saya harus memindahkan langganan dari konstruktor komponen ke ngOnInit () (komponen tersebut dibagikan melintasi rute), hanya meninggalkan sini jika ada orang yang memiliki masalah serupa
Luca
1
Saya punya masalah dengan aplikasi 5 Angular di mana saya menggunakan layanan untuk mendapatkan nilai dari api dan mengatur variabel dalam komponen yang berbeda. Saya menggunakan subyek / yang bisa diamati tetapi tidak akan mendorong nilai setelah perubahan rute. ReplaySubject adalah pengganti untuk Subjek dan menyelesaikan semuanya.
jafaircl
1
Pastikan Anda menggunakan ReplaySubject (1) jika tidak, pelanggan baru akan mendapatkan setiap nilai yang dipancarkan sebelumnya secara berurutan - ini tidak selalu jelas saat runtime
Drenai
@Drenai sejauh yang saya mengerti ReplaySubject (1) berperilaku sama dengan BehaviorSubject ()
Kfir Erez
Tidak persis sama, perbedaan besar adalah bahwa ReplaySubject tidak segera memancarkan nilai default ketika berlangganan jika next()fungsinya belum dipanggil, sedangkan BehaviourSubject tidak. Emit langsung sangat berguna untuk menerapkan nilai default ke tampilan, ketika BehaviourSubject digunakan sebagai sumber data yang dapat diamati dalam layanan misalnya
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
// .................
// .................
// .................

Anda dapat memeriksa artikel lengkap tentang cara mengimplementasikannya dari sini. https://www.imkrish.com/how-to-get-current-value-of-observable-in-a-clean-way/

Keerati Limkulphong
sumber
3

Saya mengalami masalah yang sama dalam komponen anak di mana awalnya harus memiliki nilai saat ini dari Subjek, kemudian berlangganan Subjek untuk mendengarkan perubahan. Saya hanya mempertahankan nilai saat ini di Layanan sehingga tersedia untuk komponen untuk diakses, misalnya:

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

Komponen yang membutuhkan nilai saat ini dapat mengaksesnya dari layanan, yaitu:

sessionStorage.isLoggedIn

Tidak yakin apakah ini praktik yang benar :)

Molp Burnbright
sumber
2
Jika Anda membutuhkan nilai yang dapat diamati dalam tampilan komponen Anda, Anda bisa menggunakan asyncpipa.
Ingo Bürk
2

Sebuah serupa mencari jawaban downvoted. Tapi saya pikir saya bisa membenarkan apa yang saya sarankan di sini untuk kasus-kasus terbatas.


Meskipun benar bahwa yang diamati tidak memiliki nilai saat ini , sangat sering itu akan memiliki nilai yang segera tersedia . Misalnya dengan toko redux / fluks / akita Anda dapat meminta data dari toko pusat, berdasarkan sejumlah yang dapat diobservasi dan nilai itu umumnya akan segera tersedia.

Jika demikian, maka ketika Anda subscribe, nilainya akan segera kembali.

Jadi misalkan Anda melakukan panggilan ke layanan, dan pada penyelesaian Anda ingin mendapatkan nilai terbaru dari sesuatu dari toko Anda, yang mungkin tidak akan memancarkan :

Anda mungkin mencoba melakukan ini (dan Anda harus sedapat mungkin menjaga barang-barang 'di dalam pipa'):

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

                        // we have serviceCallResponse and customer 
                     });

Masalah dengan ini adalah bahwa hal itu akan memblokir sampai yang diamati diamati nilai sekunder, yang berpotensi tidak pernah.

Saya menemukan diri saya baru-baru ini perlu mengevaluasi yang dapat diamati hanya jika nilai segera tersedia , dan yang lebih penting saya harus dapat mendeteksi jika tidak. Saya akhirnya melakukan ini:

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

Perhatikan bahwa untuk semua hal di atas saya menggunakan subscribeuntuk mendapatkan nilai (seperti @Ben membahas). Tidak menggunakan .valueproperti, bahkan jika saya punya BehaviorSubject.

Simon_Weaver
sumber
1
Btw ini berfungsi karena secara default 'jadwal' menggunakan utas saat ini.
Simon_Weaver
1

Meskipun mungkin terdengar berlebihan, ini hanyalah solusi "mungkin" untuk menjaga tipe yang dapat diamati dan mengurangi ...

Anda selalu dapat membuat pengambil ekstensi untuk mendapatkan nilai saat ini dari Observable.

Untuk melakukan ini, Anda perlu memperluas Observable<T>antarmuka dalam global.d.tsfile deklarasi pengetikan. Kemudian implementasikan pengambil ekstensi dalam sebuah observable.extension.tsfile dan akhirnya sertakan juga pengetikan dan ekstensi file ke aplikasi Anda.

Anda dapat merujuk pada Jawaban StackOverflow ini untuk mengetahui cara memasukkan ekstensi ke aplikasi Angular Anda.

// 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
sumber
0

Anda dapat menyimpan nilai yang dipancarkan terakhir secara terpisah dari Observable. Kemudian bacalah saat dibutuhkan.

let lastValue: number;

const subscription = new Service().start();
subscription
    .subscribe((data) => {
        lastValue = data;
    }
);
Slawa
sumber
1
Itu bukan pendekatan reaktif untuk menyimpan beberapa barang di luar yang dapat diamati. Alih-alih, Anda harus memiliki data sebanyak mungkin yang mengalir di dalam aliran yang dapat diamati.
ganqqwerty
0

Cara terbaik untuk melakukannya adalah menggunakan Behaviur Subject, berikut ini sebuah contoh:

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
sumber
0

Langganan dapat dibuat dan setelah barang yang dipancarkan pertama dimusnahkan. Pipa adalah fungsi yang menggunakan Observable sebagai input dan mengembalikan Observable lain sebagai output, sementara tidak memodifikasi observable pertama. 8.1.1 sudut. Paket: "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
sumber