Прежде всего, вот рабочий пример для stackblitz.
Вместо использования магазина я только что создал фиктивный класс 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 output](https://i.stack.imgur.com/slFto.png)
Если мы используем объединенные последние, мы объединяем наблюдаемые отдельно и заботимся только о последнем состоянии, что приводит к 2 вызовам 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-вызовы, завершение процесса и т. Д., если вы берете только один элемент из потока, результирующий поток завершается после одного выброса.
PS
Я рекомендую использовать async
pipe дляr работает с наблюдаемыми (как я сделал со свойствами
gasSushi: Observable<number>;
sbSushi: Observable<number>;
и внутри шаблона, затем
<div>
<h3>starbucks sushi</h3>
<p>{{sbSushi | async}}</p>
</div>
вместо
result => this.zipped = result
, который я использовалоба в этом примере, чтобы вы могли сравнить их.Исходя из моего опыта, работа с наблюдаемыми становится намного проще, если вы прекратите преобразование «отмены наблюдения» их заранее и просто дадите трубе async
выполнять свою работу.
Кроме того, если выиспользуйте subscribe
где-нибудь в вашем компоненте, вы должны unsubscribe
, когда компонент уничтожен - что совсем не сложно, но если мы никогда не подписываемся явно, и разрешаем каналу async
подписаться, он также обрабатываетуничтожение для нас:)