我有一个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的当前值。我该怎么做呢?
唯一的方法,你应该得到值“出”一个可观察对象/主题是与订阅!
如果你在使用getValue()你在做一些声明式的命令。它是一个逃生口,但是99.9%的情况下你不应该使用getValue()。getValue()会做一些有趣的事情:如果主题已经取消订阅,它会抛出一个错误,如果主题已经死亡,它会阻止你获得一个值,因为它是错误的,等等。但是,再一次地,它是在罕见情况下的一个逃生口。
有几种方法可以以“Rx-y”方式从Subject或Observable中获取最新的值:
Using BehaviorSubject: But actually subscribing to it. When you first subscribe to BehaviorSubject it will synchronously send the previous value it received or was initialized with.
Using a ReplaySubject(N): This will cache N values and replay them to new subscribers.
A.withLatestFrom(B): Use this operator to get the most recent value from observable B when observable A emits. Will give you both values in an array [a, b].
A.combineLatest(B): Use this operator to get the most recent values from A and B every time either A or B emits. Will give you both values in an array.
shareReplay(): Makes an Observable multicast through a ReplaySubject, but allows you to retry the observable on error. (Basically it gives you that promise-y caching behavior).
publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc: Other operators that leverage BehaviorSubject and ReplaySubject. Different flavors of the same thing, they basically multicast the source observable by funneling all notifications through a subject. You need to call connect() to subscribe to the source with the subject.
我在子组件中遇到了同样的问题,最初它必须拥有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
不确定这是否是正确的做法:)
有两种方法可以实现这一点。
BehaviorSubject有一个getValue()方法,您可以在特定的时间点获取值。
你可以直接订阅BehaviorSubject,也可以将订阅的值传递给类成员、字段或属性。
我不会同时推荐这两种方法。
在第一种方法中,它是一种方便的方法,您可以随时获取值,您可以将其称为该时间点的当前快照。问题是你可以在你的代码中引入竞争条件,你可以在许多不同的地方和不同的时间调用这个方法,这很难调试。
第二种方法是大多数开发人员在他们想要订阅时使用的原始值,您可以跟踪订阅,以及何时取消订阅以避免进一步的内存泄漏,如果您真的非常想将其绑定到一个变量,并且没有其他方法来连接它,则可以使用这种方法。
我建议,再看看你的用例,你在哪里使用它?例如,当你调用任何API时,你想要确定用户是否登录,你可以结合其他观察数据:
const data$ = apiRequestCall$().pipe(
// Latest snapshot from BehaviorSubject.
withLatestFrom(isLoggedIn),
// Allow call only if logged in.
filter(([request, loggedIn]) => loggedIn)
// Do something else..
);
在angular的情况下,你可以通过管道数据$ | async直接使用它到UI。
虽然这听起来有点夸张,但这只是另一个“可能”的解决方案,可以保持Observable类型并减少样板文件……
你总是可以创建一个扩展getter来获取Observable的当前值。
要做到这一点,你需要在global.d.t types声明文件中扩展Observable<T>接口。然后在observable.extension.ts文件中实现扩展getter,最后将类型和扩展文件都包含到应用程序中。
你可以参考这个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...
});