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