#rxjs #behaviorsubject #rxjs-pipeable-operators #switchmap
#rxjs #behaviorsubject #rxjs-pipeable-операторы #switchmap
Вопрос:
Предположим, что у вас есть функция, которая возвращает наблюдаемый объект rxjs, содержащий список объектов.
const getItems = () =>
of([
{
id: 1,
value: 10
},
{
id: 2,
value: 20
},
{
id: 3,
value: 30
}
]);
и вторая функция, которая возвращает наблюдаемое с одним объектом
const getItem = id =>
of({
id,
value: Math.floor(Math.random() * 30) 1
});
Теперь мы хотим создать наблюдаемый объект, который получит первый список и через регулярные промежутки времени будет случайным образом обновлять любой элемент списка.
const source = getItems().pipe(
switchMap(items =>
interval(5000).pipe(
switchMap(x => {
// pick up a random id
const rId = Math.floor(Math.random() * 3) 1;
return getItem(rId).pipe(
map(item =>
items.reduce(
(acc, cur) =>
cur.id === item.id ? [...acc, item] : [...acc, cur],
[]
)
)
);
})
)
)
);
source.subscribe(x => console.log(JSON.stringify(x)));
Проблема с приведенным выше кодом заключается в том, что каждый раз, когда запускается интервал, элементы из предыдущей итерации возвращаются к своей первоначальной форме. например,
[{"id":1,"value":10},{"id":2,"value":13},{"id":3,"value":30}]
[{"id":1,"value":10},{"id":2,"value":20},{"id":3,"value":18}]
[{"id":1,"value":10},{"id":2,"value":16},{"id":3,"value":30}]
[{"id":1,"value":21},{"id":2,"value":20},{"id":3,"value":30}]
Как вы видите, на каждом интервале наш код сбрасывает список и обновляет новый элемент (например, значение 13 теряется на второй итерации и возвращается к 20).
Поведение кажется разумным, поскольку items
аргумент в первом switchMap
действует как замыкание.
Мне удалось каким-то образом решить проблему с помощью BehaviorSubject
, но я думаю, что мое решение как-то грязно.
const items$ = new BehaviorSubject([]);
const source = getItems().pipe(
tap(items => items$.next(items)),
switchMap(() =>
interval(5000).pipe(
switchMap(() => {
const rId = Math.floor(Math.random() * 3) 1;
return getItem(rId).pipe(
map(item =>
items$
.getValue()
.reduce(
(acc, cur) =>
cur.id === item.id ? [...acc, item] : [...acc, cur],
[]
)
),
tap(items => items$.next(items)),
switchMap(() => items$)
);
})
)
)
);
Есть ли лучший подход?
Пример кода можно найти здесь
Ответ №1:
Я считаю, что это должно делать то, что вы хотите:
const source = getItems().pipe(
switchMap(items =>
interval(1000).pipe(
switchMap(() => {
const rId = Math.floor(Math.random() * 3) 1;
return getItem(rId);
}),
scan((acc, item) => {
acc[acc.findIndex(i => i.id === item.id)] = item;
return acc;
}, items),
)
)
);
Это в основном то, что вы делаете, но я использую scan
(который инициализируется оригиналом items
), чтобы сохранить выходной массив, acc
чтобы я мог обновить его позже снова.
Живая демонстрация: https://stackblitz.com/edit/rxjs-kvygy1?file=index.ts
Комментарии:
1. Спасибо! Я также безуспешно пробовал оператор сканирования