Як отримати поточне значення предмета RxJS або спостережуваного?


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

SubjectАбо Observableне має значення струму. Коли значення випромінюється, воно передається абонентам іObservable це робиться з ним.

Якщо ви хочете мати поточне значення, використовуйте BehaviorSubjectяке розроблено саме для цієї мети.BehaviorSubjectзберігає останнє випромінене значення і негайно передає його новим абонентам.

Він також має метод getValue()отримання поточного значення.


1
Привіт, мені погано, вони те саме, що зараз у rxjs 5. Посилання на вихідний код вище посилалось на rxjs4.
код шродінгера

84
Важлива примітка автора RxJS 5: Використовуючи ВЕЛИЧИЙ getValue()червоний прапор, ви робите щось не так. Це там, як люк для втечі. Як правило, все, що ви робите з RxJS, має бути декларативним. getValue()є імперативом. Якщо ви користуєтесь getValue(), є 99,9% шансів, що ви зробите щось не так або дивно.
Бен Леш

1
@BenLesh Що робити, якщо я маю BehaviorSubject<boolean>(false)та хотів би перемикати його?
боб

3
@BenLesh getValue () дуже корисний для того, щоб сказати, виконуючи миттєві дії, як onClick-> dispatchToServer (x.getValue ()), але не використовуйте його у спостережних ланцюгах.
FlavorScape

2
@ IngoBürk Єдиним способом я можу виправдати вашу пропозицію, якщо ви не знали, що це foo$було BehaviorSubject(тобто воно визначається як, Observableа може бути, воно гаряче або холодно з відкладеного джерела). Однак , так як ви потім перейти до використання .nextна $fooсобі , що означає , що ваша реалізація залежить від нього бутиBehaviorSubject так що немає ніяких підстав для не тільки з допомогою , .valueщоб отримати поточне значення в першу чергу.
Simon_Weaver

145

Єдиний спосіб, коли вам слід отримати значення "з" спостережуваного / тематичного, - це підписатися!

Якщо ви використовуєте, 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(): Здійснює спостережливе багатоадресневе повідомлення через ReplaySubject, але дозволяє повторити спробу, що спостерігається помилково. (В основному це дає вам таку обіцянку - кешування поведінки).
  6. publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject)І т.д .: Інші оператори , які використовують BehaviorSubjectі ReplaySubject. Різні смаки одного і того ж, вони, в основному, передають джерело, яке можна побачити, перенаправляючи всі сповіщення через тему. Вам потрібно зателефонувати, connect()щоб підписатися на джерело з темою.

19
Ви можете розробити, чому використання getValue () - це червоний прапор? Що робити, якщо мені потрібне поточне значення спостережуваного лише один раз, в момент, коли користувач натискає кнопку? Чи варто підписатися на значення та негайно скасувати підписку?
Полум’я

5
Іноді це нормально. Але часто це знак того, що автор коду робить щось дуже імперативне (зазвичай із побічними ефектами) там, де вони не повинні бути. Ви також можете click$.mergeMap(() => behaviorSubject.take(1))вирішити свою проблему.
Бен Леш

1
Чудове пояснення! Насправді, якщо ви можете зберегти спостережливість і використовувати методи, це набагато кращий спосіб. У контексті машинописного тексту, що використовується Observable, що використовується скрізь, але лише декілька, визначений як BehaviourSubject, тоді це буде менш константний код. Запропоновані вами методи дозволили мені зберігати види, що спостерігаються скрізь.
JLavoie

2
добре, якщо ви хочете просто відправити поточне значення (наприклад, надіслати його на сервер при натисканні), зробити getValue () дуже зручно. але не використовуйте його під час зв’язування операторів, які спостерігаються.
FlavorScape

1
У мене є запит, AuthenticationServiceякий використовує a BehaviourSubjectдля зберігання поточного входу в стан ( boolean trueабо false). Це відкриває isLoggedIn$спостереження для абонентів, які хочуть знати, коли стан змінюється. Він також відкриває get isLoggedIn()властивість, яка повертає поточний зареєстрований стан, за допомогою виклику getValue()нижнього BehaviourSubject- це використовується моїм захисником автентифікації для перевірки поточного стану. Це здається мені розумним використанням getValue()...?
Дан Кінг

12

У мене була подібна ситуація, коли пізні абоненти підписуються на Тему після надходження її вартості.

Я знайшов ReplaySubject, який схожий на BehaviorSubject, працює як шарм у цьому випадку. Ось посилання на краще пояснення: http://reactivex.io/rxjs/manual/overview.html#replaysubject


1
це допомогло мені в моєму додатку Angular4 - мені довелося перенести підписку з конструктора компонентів на ngOnInit () (такий компонент ділиться через маршрути), просто виїжджаючи звідси у випадку, якщо хтось має подібну проблему
Лука

1
У мене виникла проблема з додатком Angular 5, де я використовував сервіс для отримання значень з api та встановлення змінних у різних компонентах. Я використовував предмети / спостережувані дані, але вони не зміщуватимуть значення після зміни маршруту. ReplaySubject був краплею заміною для Subject і вирішив усе.
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

Я зіткнувся з тією ж проблемою в дочірніх компонентах, де спочатку він повинен був мати поточне значення 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
Якщо вам потрібне значення спостережуваного в перегляді компонента, ви можете просто використовувати asyncтрубу.
Інго Бюрк

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
Btw це працює, тому що за замовчуванням 'графік' використовує поточну нитку.
Simon_Weaver

1

Хоча це може здатися надмірним, це просто ще одне "можливе" рішення для збереження типу " Спостережуваність" та зменшення котлової панелі ...

Ви завжди можете створити розширення, щоб отримати поточне значення спостережуваного.

Для цього вам потрібно буде розширити Observable<T>інтерфейс у global.d.tsфайлі декларації про типізацію. Потім реалізуйте геттер розширення у observable.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

Ви можете зберігати останнє випромінене значення окремо від спостеріганого. Потім прочитайте його, коли потрібно.

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

Підписку можна створити і після зняття першого випущеного елемента знищити. Труба - це функція, яка використовує спостережуваний як свій вхід і повертає інший спостережуваний як вихід, не змінюючи першого спостережуваного. Кутовий 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);
      });

    }
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.