#angular #functional-programming #rxjs #observable
#angular #функциональное программирование #rxjs #наблюдаемый
Вопрос:
Я хочу реализовать прослушиватель прокрутки для своего сайта с помощью rxjs. В настоящее время прослушиватель выдает каждое число scrollY. Возможно ли реализовать прослушиватель прокрутки, который выдает только позицию прокрутки, если число больше, чем раньше, БЕЗ СОХРАНЕНИЯ СВОЙСТВА STATEFULL. Я хотел бы решить это только с помощью операторов. Текущая реализация выглядит следующим образом:
public lastScrolledHeight: number = 0;
...
public ngOnInit() {
this.listenForScroll()
.subscribe(scrollHeight => {
this.showAddButton = this.lastScrolledHeight >= scrollHeight; // true or false
this.lastScrolledHeight = scrollHeight;
});
}
private listenForScroll(): Observable<number> {
return fromEvent(window, 'scroll').pipe(
debounceTime(25),
map((e: any) => e.path[1].scrollY)
);
}
подсказки
Одним из подходов уже может быть добавление startsWith(0)
оператора. Это приведет к тому, что начальная позиция будет равна 0. Но если scan()
filter()
или reduce()
поможет, я не могу сказать.
usecase
Я прокручиваю вниз до Y = 300. должно быть выдано 300. Я прокручиваю до Y = 50. Ничего не должно выдаваться. Я снова прокручиваю вниз до 150, должно быть выдано 150.
Ответ №1:
Вы можете использовать scan
оператор вместе с distinctUntilChanged
:
return fromEvent(window, 'scroll').pipe(
debounceTime(25),
map((e: any) => e.path[1].scrollY),
scan((prev, curr) => Math.max(prev, curr), 0),
distinctUntilChanged()
)
Что происходит, так это то, что наблюдаемое изменяется, чтобы содержать максимальное значение текущей и предыдущей итерации (и имеет значение 0 в качестве начального значения).
После этого проверяется, distinctUntilChanged()
что наблюдаемые повторяющиеся события не генерируются.
Это гарантирует, что вы будете получать только значения, которые больше предыдущего.
Комментарии:
1. Хотя мне действительно понравилась простота этого подхода, кажется, что он будет выдавать только увеличивающуюся позицию прокрутки и не будет выдавать, если пользователь будет прокручивать вниз, затем вверх, затем снова вниз, см. Этот пример . Тем не менее, хорошее использование
scan
здесь
Ответ №2:
Я думаю, вы могли бы использовать для этого попарно:
source$.pipe(
startWith(-1),
pairwise(),
switchMap(([a,b])=>
b > a
? of(b)
: EMPTY
)
)
Проверьте этот код на игровой площадке
Надеюсь, это поможет
Комментарии:
1. Я проверю это как можно скорее! Забавный факт: часть моей бакалаврской диссертации была посвящена RxJS, и все еще непонятно, насколько я все еще не знаю. ^^
2. @creep-story, да, в FRP так много всего! Обязательно проверьте ту игровую площадку, на которую я ссылался — это может помочь вам лучше просматривать Rx и другие библиотеки FRP! Кроме того, теперь я думаю, что вариант, предложенный Даниэлем, выглядит лучше.
3. @creep-story, я добавил еще один ответ с помощью пользовательского оператора. Я думаю, что этот подход должен быть предпочтительнее этого. / на мгновение я подумал, что простой distinctUntilChanged с предикатом решит вашу проблему, но оказалось, что он сравнивает ранее переданное значение с новым, в то время как вам нужно сравнить любое предыдущее значение с новым /
4. ваша оценка отлично работает с пользовательским оператором, однако я бы оценил, если вы также ознакомитесь с моим решением. 🙂
Ответ №3:
Хотя я так благодарен за помощь @Kos и @Daniel за то, что они потратили время, чтобы помочь мне найти чистое решение, я нашел подход, который является чистым и простым.
fromEvent(document, 'scroll').pipe(
debounceTime(50),
// get scrollY
map((e: any) => e.path[1].scrollY),
startWith(0),
distinctUntilChanged(),
// map the last scroll values into an array
pairwise(),
// returns true if delta of prev amp; curr is greaterOrEqual 0 => scroll up
map(([prev, curr]: Array<number>) => prev - curr >= 0)
);
Комментарии:
1. Ну, это совершенно круто, если вы собираетесь сделать if-else позже. Пара вещей: вы, вероятно, захотите переместиться
distinctUntilChanged
вниз — afaik, документ не будет генерировать событие прокрутки без изменений. ПоместитеstartWith
после карты и простоstartWith(0)
. И вам определенно не нужноBoolean(..)
, простоprev-curr >= 0
это уже логическое значение2. Да, ты прав, лол. ^^ Но мне больше не нужно ничего делать, если / else. 🙂
3. ах, ты имеешь в виду это … да, моя подписка выглядит так: …
.subscribe(show => this.showAddButton = show);
4. Круто 🙂 Да, но наши решения были направлены на фильтрацию, а не на отображение. Получайте удовольствие от Rx-ing!
5.
distinctUntilChanged()
может быть полезно в ситуации, когда есть также вертикальная прокрутка, в этом случае события будут запускаться с тем жеscrollY
значением. Я бы просто изменил карту внизуfilter(([prev, next]) => next > prev)
, чтобы наблюдаемая не выдавала все значения, только те, которые выше. Если я не понял что-то не так 🙂
Ответ №4:
Будучи неудовлетворенным моим предыдущим подходом, я решил создать пользовательский оператор, который охватывает rxjs.filter
и будет использовать предикат для сравнения текущего значения с предыдущим:
// it will take a predicate to compare values
// by default it will behave as distinctUntilChanged()
const filterChanges = (predicate = ((a,b) => a!==b)) => {
// store previous value
let prevValue = void 0;
return pipe(
filter((value, index)=>{
// pass through the first value on stream
if (index === 0) {
prevValue = value;
return value;
}
// compare current with prev
const result = predicate(value, prevValue);
prevValue = value;
return resu<
})
);
};
И тогда это так же просто, как передача компаратора:
source$.pipe(
filterChanges((a, b) => a > b)
)
Вывод:
Надеюсь, это поможет