Подтвердить что ты не робот

Как получить текущее значение предмета или наблюдаемого объекта RxJS?

У меня есть служба 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 в определенный момент времени. Как я могу это сделать?

4b9b3361

Ответ 1

A Subject или Observable не имеет текущего значения. Когда значение испускается, оно передается подписчикам, а Observable выполняется с ним.

Если вы хотите иметь текущее значение, используйте BehaviorSubject, который предназначен именно для этой цели. BehaviorSubject сохраняет последнее испущенное значение и немедленно отправляет его новым подписчикам.

Он также имеет метод getValue() для получения текущего значения.

Ответ 2

Единственный способ, которым вы должны получать значения "из" наблюдаемого/субъекта, - подписаться!

Если вы используете getValue(), вы делаете что-то императивное в декларативной парадигме. Это там как побег-люк, но 99,9% времени, когда вы НЕ должны использовать getValue(). Есть несколько интересных вещей, которые getValue() будет делать: он выдает ошибку, если объект имеет был отменен, это не позволит вам получить значение, если объект мертв, потому что он ошибся и т.д. Но, опять же, он там, как спасательный люк для редких обстоятельств.

Существует несколько способов получить последнее значение от объекта или наблюдаемого по методу "Rx-y":

  • Использование BehaviorSubject: на самом деле подписка на него. Когда вы впервые подписываетесь на BehaviorSubject, он будет синхронно отправлять предыдущее значение, полученное или инициализированное.
  • Использование ReplaySubject(N): это кеширует значения N и воспроизводит их для новых подписчиков.
  • A.withLatestFrom(B): Используйте этот оператор, чтобы получить последнее значение от наблюдаемого B, когда наблюдается наблюдаемый A. Дает вам оба значения в массиве [a, b].
  • A.combineLatest(B). Используйте этот оператор, чтобы получать самые последние значения из A и B каждый раз, когда испускаются A или B. Дает вам оба значения в массиве.
  • shareReplay(): делает многостраничную наблюдение через ReplaySubject, но позволяет повторить наблюдаемую при ошибке. (В основном это дает вам такое обещание-кэширование).
  • publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject) и т.д. Другие операторы, которые используют BehaviorSubject и ReplaySubject. Разные вкусы одного и того же, они в основном многоадресный источник, наблюдаемый путем перенаправления всех уведомлений через предмет. Вам нужно позвонить connect(), чтобы подписаться на источник с темой.

Ответ 3

У меня была похожая ситуация, когда поздние подписчики подписывались на Предмет после того, как его ценность прибыла.

Я обнаружил, что ReplaySubject, который похож на BehaviorSubject, в этом случае работает как шарм. А вот ссылка на лучшее объяснение: http://reactivex.io/rxjs/manual/overview.html#replaysubject

Ответ 4

Я столкнулся с той же проблемой в дочерних компонентах, где изначально она должна была иметь текущее значение 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

Не уверен, что это правильная практика:)

Ответ 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/

Ответ 6

Аналогичный ответ был отклонен. Но я думаю, что могу оправдать то, что я предлагаю здесь для ограниченных случаев.


Хотя верно, что наблюдаемая не имеет текущего значения, очень часто она будет иметь немедленно доступное значение. Например, в хранилищах 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 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.

Ответ 7

Вы можете хранить последнее переданное значение отдельно от Observable. Тогда прочитайте это когда необходимо.

let lastValue: number;

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

Ответ 8

Хотя это может показаться излишним, но это еще одно "возможное" решение, позволяющее сохранить тип Observable и уменьшить количество шаблонов...

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

Для этого вам необходимо расширить интерфейс Observable<T> в файле объявления типографий global.d.ts. Затем внедрите средство получения расширений в файл observable.extension.ts и, наконец, включите в свое приложение и набор, и файл расширения.

Вы можете обратиться к этому Qaru Answer, чтобы узнать, как включить расширения в ваше приложение 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...
  });

Ответ 9

Лучший способ сделать это - использовать 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