我正在将Angular 7与ngrx存储一起使用。商店包含应用程序状态,我正在OnInit的应用程序组件中订阅该状态。存储中有几个变量是可以互换的(用按钮交换)。
这是我在组件中的示例代码。
this.appState.getGasStationSushi().pipe(switchMap((sushi) => {
this.gsSushi = sushi;
return this.appState.getStarbucksSushi();
}), switchMap((sushi) => {
this.sbSushi = sushi;
return this.service.compare(this.gsSushi, this.sbSushi);
}).subscribe((result)=>{
console.log(result);
}));
在视图中单击按钮,用户可以更改两个sushi
值,这将导致最后一次订阅调用两次,这很有意义(RxJS)。我可以删除switchMap
并写类似
-- gas station sushi susbcription
-- star bucks sushi susbcription
-- compare
我不是真的很喜欢这个,我确信一定有一个rxjs/operator
。有人可以提个建议吗?
也尝试过forkjoin
,但是使用ngrx存储似乎需要像下面这样调用first
或last
。这是上述声明forkjoinWithstore
const $sushiObs = [
this.appState.getGasStationSushi().pipe(first()),
this.appState.getStarbucksSushi().pipe(first())
];
forkjoin($sushiObs).subscribe((result) => {
console.log(result);
});
如果我使用上述模式,则订阅将首次触发,但之后不会触发。
答案 0 :(得分:1)
首先,这是一个working example的堆叠闪电战。
我没有创建商店,而是创建了一个返回观察值的模拟类SushiState
。
class SushiState {
private _gas = new BehaviorSubject(1);
private _starbucks = new BehaviorSubject(1);
public get gas() {
return this._gas.asObservable();
}
public get starbucks() {
return this._gas.asObservable();
}
public increaseSushi(n = 1) {
this._gas.next(this._gas.value + n);
this._starbucks.next(this._starbucks.value + n);
}
public static compareSushi(g: number, s: number): string {
return `gas is ${g}, starbucks is ${s}`;
}
}
关于组件,这是代码。
export class AppComponent implements OnInit {
state: SushiState;
gasSushi: Observable<number>;
sbSushi: Observable<number>;
combined: string;
combinedTimes = 0;
zipped: string;
zippedTimes = 0;
ngOnInit() {
this.state = new SushiState;
this.gasSushi = this.state.gas;
this.sbSushi = this.state.gas;
const combined = combineLatest(
this.gasSushi,
this.sbSushi,
).pipe(
tap((sushi) => {
console.log('combined', sushi);
this.combinedTimes++;
}),
map((sushi) => SushiState.compareSushi(sushi[0], sushi[1])),
);
combined.subscribe(result => this.combined = result);
const zipped = zip(
this.gasSushi,
this.sbSushi,
).pipe(
tap((sushi) => {
console.log('zipped', sushi);
this.zippedTimes++;
}),
map((sushi) => SushiState.compareSushi(sushi[0], sushi[1])),
);
zipped.subscribe(result => this.zipped = result);
}
increaseSushi() {
this.state.increaseSushi();
}
}
如果在stackblitz上运行它并检查控制台,您将看到以下行为:
如果我们使用最新的组合,那么我们将可观察的事物单独组合,只关心最新状态,从而导致两次console.log
的调用。
我们可以改用zip
,它在产生输出之前等待两个可观察对象都发出。这看起来很适合我们的“同时增加”按钮,但是有一个问题:如果starbucksSushi
单独增加(可能来自应用程序的不同部分),则zipped
版本将等待加油站寿司也要更新。
要提出第三个解决方案,您可以使用combineLatest
来组合寿司计数器,然后使用debounceTime
运算符等待一定的毫秒数,然后发出输出。
const debounced = zip(
this.gasSushi,
this.sbSushi,
).pipe(
tap((sushi) => {
console.log('debounced', sushi);
this.debouncedTimes++;
}),
map((sushi) => SushiState.compareSushi(sushi[0], sushi[1])),
debounceTime(100),
);
debounced.subscribe(result => this.debounced = result);
这将对所有源中的更改做出反应,但不会比100ms
更频繁。
最后,您必须做first()
的原因:
forkJoin
在可观察对象完成后加入(只能发生一次,因此不适合连续播放),并且更适合“承诺式”工作,例如HTTP调用,流程完成等。顺便说一句,如果仅从流中获取一个元素,则生成的流将在一次发射后完成。
我建议利用async
管道来处理可观察对象(就像我对属性所做的那样
gasSushi: Observable<number>;
sbSushi: Observable<number>;
然后在模板内
<div>
<h3>starbucks sushi</h3>
<p>{{sbSushi | async}}</p>
</div>
代替
result => this.zipped = result
在此示例中,我已经使用了这两种方法,因此可以对其进行比较。根据我的经验,一旦停止提前转换“取消观测”,而仅允许async
管道完成其工作,使用可观测对象将变得更加容易。
最重要的是,如果您在组件的某个位置使用subscribe
,则在组件销毁时应该unsubscribe
-一点也不难,但是如果我们从不明确地订阅并允许async
管道进行订阅,它也为我们处理破坏:)