Làm thế nào để có được giá trị hiện tại của Chủ đề RxJS hoặc có thể quan sát được?


206

Tôi có dịch vụ 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);
    });
  }
}

Mọi thứ hoạt động tuyệt vời. Nhưng tôi có một thành phần khác không cần đăng ký, nó chỉ cần nhận giá trị hiện tại của isLoggedIn tại một thời điểm nhất định. Tôi có thể làm cái này như thế nào?

Câu trả lời:


341

A Subjecthoặc Observablekhông có giá trị hiện tại. Khi một giá trị được phát ra, nó được chuyển đến các thuê bao và Observablenó được thực hiện với nó.

Nếu bạn muốn có một giá trị hiện tại, hãy sử dụng giá trị BehaviorSubjectđược thiết kế cho mục đích đó.BehaviorSubjectgiữ giá trị phát ra cuối cùng và phát ra ngay lập tức cho các thuê bao mới.

Nó cũng có một phương pháp getValue()để có được giá trị hiện tại.


1
Xin chào, đó là điểm xấu của tôi, giờ đây chúng giống nhau trong rxjs 5. Liên kết mã nguồn ở trên đã đề cập đến rxjs4.

84
Lưu ý quan trọng từ tác giả của RxJS 5: Sử dụng getValue()là một lá cờ đỏ LỚN bạn đang làm gì đó sai. Đó là một lối thoát. Nói chung mọi thứ bạn làm với RxJS nên được khai báo. getValue()Là bắt buộc. Nếu bạn đang sử dụng getValue(), có 99,9% khả năng bạn đang làm gì đó sai hoặc lạ.
Ben Lesh

1
@BenLesh nếu tôi có BehaviorSubject<boolean>(false)và muốn chuyển đổi thì sao?
bob

3
@BenLesh getValue () rất hữu ích để nói, thực hiện một hành động tức thời, như onClick-> ftimeToServer (x.getValue ()) nhưng không sử dụng nó trong các chuỗi có thể quan sát được.
FlavorScape

2
@ IngoBürk Cách duy nhất tôi có thể biện minh cho đề xuất của bạn là nếu bạn không biết đó foo$là một BehaviorSubject(nghĩa là nó được định nghĩa là Observablevà có thể nó nóng hoặc lạnh từ một nguồn bị trì hoãn). Tuy nhiên kể từ khi bạn tiếp tục chuyển sang sử dụng .nextvào $foobản thân có nghĩa là thực hiện của bạn dựa trên đó một BehaviorSubjectnên không biện minh cho việc không chỉ sử dụng .valueđể có được giá trị hiện tại ở nơi đầu tiên.
Simon_Weaver

145

Cách duy nhất bạn nên nhận được các giá trị "ngoài" một Chủ đề / Quan sát là đăng ký!

Nếu bạn đang sử dụng, getValue()bạn đang làm một cái gì đó bắt buộc trong mô hình khai báo. Đó là một lối thoát, nhưng 99,9% thời gian bạn KHÔNG nên sử dụng getValue(). Có một vài điều thú vị màgetValue() sẽ làm: Nó sẽ báo lỗi nếu đối tượng đã bị hủy đăng ký, nó sẽ khiến bạn không nhận được giá trị nếu đối tượng đã chết vì nó bị lỗi, v.v. Nhưng, một lần nữa, đó là một lối thoát nở cho hoàn cảnh hiếm.

Có một số cách để nhận giá trị mới nhất từ ​​Chủ thể hoặc Có thể quan sát theo cách "Rx-y":

  1. Sử dụng BehaviorSubject: Nhưng thực sự đăng ký nó . Khi bạn đăng ký lần đầuBehaviorSubject nó sẽ gửi đồng bộ giá trị trước đó mà nó nhận được hoặc được khởi tạo.
  2. Sử dụng một ReplaySubject(N): Điều này sẽ bộ nhớ cacheN các giá trị và phát lại chúng cho những người đăng ký mới.
  3. A.withLatestFrom(B): Sử dụng toán tử này để nhận giá trị gần đây nhất có thể quan sát được Bkhi Aphát ra có thể quan sát được . Sẽ cung cấp cho bạn cả hai giá trị trong một mảng [a, b].
  4. A.combineLatest(B): Sử dụng toán tử này để có được những giá trị mới nhất từ ABmỗi khi một trong hai Ahoặc Bphát ra. Sẽ cung cấp cho bạn cả hai giá trị trong một mảng.
  5. shareReplay(): Làm cho một multicast có thể quan sát được thông qua a ReplaySubject, nhưng cho phép bạn thử lại lỗi có thể quan sát được. (Về cơ bản nó cung cấp cho bạn hành vi bộ nhớ đệm hứa hẹn-y).
  6. publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), Vv: nhà khai thác khác có đòn bẩy BehaviorSubjectReplaySubject. Các hương vị khác nhau của cùng một thứ, về cơ bản, chúng phát đa hướng nguồn có thể quan sát được bằng cách chuyển tất cả các thông báo qua một chủ đề. Bạn cần gọi connect()để đăng ký nguồn với chủ đề.

19
bạn có thể giải thích tại sao sử dụng getValue () là cờ đỏ không? Điều gì sẽ xảy ra nếu tôi chỉ cần giá trị hiện tại của mức có thể quan sát được một lần, thời điểm người dùng nhấp vào nút? Tôi có nên đăng ký giá trị và hủy đăng ký ngay lập tức?
Ngọn lửa

5
Đôi khi nó ổn. Nhưng thường thì đó là một dấu hiệu cho thấy tác giả của mã đang làm điều gì đó rất cấp bách (thường là với các tác dụng phụ) mà họ không nên làm. Bạn có thể làm click$.mergeMap(() => behaviorSubject.take(1))để giải quyết vấn đề của bạn là tốt.
Ben Lesh

1
Giải thích tuyệt vời! Trên thực tế, nếu bạn có thể giữ Quan sát và sử dụng các phương pháp, đó là cách tốt hơn nhiều. Trong ngữ cảnh của bản thảo với Observable được sử dụng ở mọi nơi nhưng chỉ có một vài định nghĩa là BehaviourSubject, thì đó sẽ là mã ít nhất quán hơn. Các phương pháp bạn đề xuất cho phép tôi giữ các loại Có thể quan sát được ở mọi nơi.
JLavoie

2
thật tốt nếu bạn muốn gửi giá trị hiện tại (như gửi nó đến máy chủ khi nhấp chuột), thực hiện getValue () rất tiện dụng. nhưng không sử dụng nó khi xâu chuỗi các toán tử quan sát được.
FlavorScape

1
Tôi có một AuthenticationServicecái sử dụng BehaviourSubjectđể lưu trữ trạng thái đã đăng nhập hiện tại ( boolean truehoặc false). Nó cho thấy một isLoggedIn$quan sát cho những người đăng ký muốn biết khi nào trạng thái thay đổi. Nó cũng hiển thị một thuộc get isLoggedIn()tính, trả về trạng thái đã đăng nhập hiện tại bằng cách gọi getValue()vào bên dưới BehaviourSubject- điều này được sử dụng bởi người bảo vệ xác thực của tôi để kiểm tra trạng thái hiện tại. Đây có vẻ như là một cách sử dụng hợp lý đối getValue()với tôi ...?
Dan King

12

Tôi đã gặp tình huống tương tự khi những người đăng ký muộn đăng ký Chủ đề sau khi giá trị của nó đến.

Tôi thấy ReplaySubject tương tự như BehaviorSubject hoạt động giống như một bùa mê trong trường hợp này. Và đây là một liên kết để giải thích tốt hơn: http://reactivex.io/rxjs/manual/overview.html#replaysubject


1
điều đó đã giúp tôi trong ứng dụng Angular4 của mình - Tôi đã phải chuyển đăng ký từ nhà xây dựng thành phần sang ngOnInit () (thành phần đó được chia sẻ trên các tuyến đường), chỉ cần rời khỏi đây trong trường hợp ai đó có vấn đề tương tự
Luca

1
Tôi gặp sự cố với ứng dụng Angular 5 khi tôi đang sử dụng dịch vụ để nhận các giá trị từ api và đặt các biến trong các thành phần khác nhau. Tôi đã sử dụng các đối tượng / vật quan sát nhưng nó sẽ không đẩy các giá trị sau khi thay đổi tuyến đường. ReplaySubject là một sự thay thế cho Chủ đề và giải quyết mọi thứ.
jafaircl

1
Đảm bảo bạn đang sử dụng ReplaySubject (1) nếu không những người đăng ký mới sẽ nhận được mọi giá trị được phát ra trước đó theo trình tự - điều này không phải lúc nào cũng rõ ràng trong thời gian chạy
Drenai

@Drenai theo như tôi hiểu thì ReplaySubject (1) hành xử giống như BehaviorSubject ()
Kfir Erez

Không hoàn toàn giống nhau, sự khác biệt lớn là ReplaySubject không ngay lập tức phát ra một giá trị mặc định khi nó được đăng ký nếu next()chức năng của nó chưa được gọi, trong khi BehaviourSubject thì không. Việc phát ra ngay lập tức rất thuận tiện để áp dụng các giá trị mặc định cho chế độ xem, khi BehaviourSubject được sử dụng làm nguồn dữ liệu có thể quan sát được trong một dịch vụ chẳng hạn
Drenai 23/03/19

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

Bạn có thể kiểm tra toàn bộ bài viết về cách thực hiện nó từ đây. https://www.imkrish.com/how-to-get-civerse-value-of-observable-in-a-clean-way/


3

Tôi gặp phải vấn đề tương tự trong các thành phần con, ban đầu nó sẽ phải có giá trị hiện tại của Chủ đề, sau đó đăng ký Chủ đề để lắng nghe các thay đổi. Tôi chỉ duy trì giá trị hiện tại trong Dịch vụ để các thành phần có thể truy cập, ví dụ:

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

Một thành phần cần giá trị hiện tại có thể truy cập nó từ dịch vụ, nghĩa là:

sessionStorage.isLoggedIn

Không chắc đây có phải là cách làm đúng không :)


2
Nếu bạn cần giá trị của một quan sát trong chế độ xem thành phần của bạn, bạn chỉ có thể sử dụng asyncđường ống.
Ingo Bürk

2

Một tương tự tìm câu trả lời đã được downvoted. Nhưng tôi nghĩ rằng tôi có thể biện minh cho những gì tôi đề nghị ở đây cho các trường hợp hạn chế.


Mặc dù đúng là một vật quan sát không có giá trị hiện tại , nhưng thường thì nó sẽ có giá trị ngay lập tức . Ví dụ: với các cửa hàng redux / flux / akita, bạn có thể yêu cầu dữ liệu từ một cửa hàng trung tâm, dựa trên một số vật quan sát và giá trị đó thường sẽ có sẵn ngay lập tức.

Nếu đây là trường hợp sau đó khi bạn subscribe, giá trị sẽ trở lại ngay lập tức.

Vì vậy, giả sử bạn đã có một cuộc gọi đến một dịch vụ và khi hoàn thành, bạn muốn nhận được giá trị mới nhất của một thứ gì đó từ cửa hàng của mình, điều đó có khả năng không thể phát ra :

Bạn có thể cố gắng làm điều này (và bạn nên giữ càng nhiều càng tốt để giữ mọi thứ 'bên trong đường ống'):

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

                        // we have serviceCallResponse and customer 
                     });

Vấn đề với điều này là nó sẽ chặn cho đến khi thứ cấp có thể quan sát được phát ra một giá trị, có khả năng không bao giờ có thể.

Gần đây tôi thấy mình chỉ cần đánh giá một giá trị có thể quan sát được nếu một giá trị có sẵn ngay lập tức và quan trọng hơn là tôi cần có khả năng phát hiện nếu không. Tôi đã kết thúc việc này:

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

Lưu ý rằng đối với tất cả những điều trên tôi đang sử dụng subscribeđể nhận giá trị (như @Ben thảo luận). Không sử dụng một .valuetài sản, ngay cả khi tôi có một BehaviorSubject.


1
Btw này hoạt động vì theo mặc định 'lịch trình' sử dụng chuỗi hiện tại.
Simon_Weaver

1

Mặc dù nghe có vẻ quá mức, đây chỉ là một giải pháp "có thể" khác để giữ loại Có thể quan sát và giảm nồi hơi ...

Bạn luôn có thể tạo một trình mở rộng tiện ích để nhận giá trị hiện tại của một Đài quan sát.

Để làm điều này, bạn sẽ cần mở rộng Observable<T>giao diện trong một global.d.tstệp khai báo đánh máy. Sau đó, thực hiện trình mở rộng getter trong một observable.extension.tstệp và cuối cùng bao gồm cả kiểu chữ và tệp mở rộng cho ứng dụng của bạn.

Bạn có thể tham khảo Câu trả lời StackOverflow này để biết cách đưa các phần mở rộng vào ứng dụng Angular của bạn.

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

Bạn có thể lưu trữ giá trị phát ra cuối cùng riêng biệt từ Đài quan sát. Sau đó đọc nó khi cần thiết.

let lastValue: number;

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

1
Đây không phải là một cách tiếp cận phản ứng để lưu trữ một số thứ bên ngoài có thể quan sát được. Thay vào đó, bạn nên có càng nhiều dữ liệu càng tốt chảy bên trong các luồng có thể quan sát được.
ganqqwerty

0

Cách tốt nhất để làm điều này là sử dụng Behaviur Subject, đây là một ví dụ:

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

Một thuê bao có thể được tạo và sau khi lấy mục phát ra đầu tiên bị phá hủy. Ống là một chức năng sử dụng một Observable làm đầu vào của nó và trả về một Observable khác như đầu ra, trong khi không sửa đổi quan sát đầu tiên. Góc 8.1.0. Gói: "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);
      });

    }
  }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.