#angular #rxjs
Вопрос:
Я пытаюсь запустить модал через 5 секунд, если пользователь остается неактивным, и написал для этого сервис, содержащий этот код:
public initScreenListen(): void {
this.mergedObservable$ = merge(
fromEvent(document, BanKeyboardEvent.KEY_DOWN),
fromEvent(document, BanMouseEvent.MOUSE_DOWN),
fromEvent(document, BanMouseEvent.MOUSE_MOVE),
fromEvent(document, BanMouseEvent.WHEEL),
fromEvent(document, BanMouseEvent.CLIK),
fromEvent(document, BanMouseEvent.MOUSE_MOVE),
fromEvent(window, BanMouseEvent.MOUSE_MOVE)
);
this.startTimer();
}
public startTimer(): void {
this.createObserable();
console.log('subscription started');
}
public createObserable(): void {
this.ngZone.runOutsideAngular(() => {
this.observeable$ = this.mergedObservable$.pipe(
switchMap((ev) => interval(1000).pipe(take(this.inactivityTime))),
tap((value) => this.isItTimeToShowPopUp(value)),
skipWhile((x) => {
this.timeLapsedSinceInactivity = x;
return x !== this.inactivityTime - 1;
})
);
});
}
public isItTimeToShowPopUp(val: number): void {
this.timeLeftForInactive = this.inactivityTime - val;
if (this.timeLeftForInactive <= 13) {
this.timeLapsedSinceInactivity = this.timeLeftForInactive;
this.ref.tick();
console.log(this.timeLeftForInactive);
}
}
и я написал это на самом компоненте:
public ngOnInit(): void {
this.inactivityListenService.initScreenListen();
const subcription = this.inactivityListenService.observeable$.subscribe(
() => {
subcription.unsubscribe();
console.log('here show modal.....');
this.inactivityListenService.initScreenListen();
}
);
}
и это работает только в первый раз, после этого он начинает вести себя беспорядочно.
Не могли бы вы помочь мне, пожалуйста?
Спасибо.
Комментарии:
1. Вы пробовали это npmjs.com/package/@ng-idle/core модуль ?
Ответ №1:
Я думаю, что у вас есть две проблемы в вашей реализации:
- Вы не
unsubscribe
должны из основногоobservable
, чтобы он работал все время, тогда нет необходимости снова вызыватьthis.inactivityListenService.initScreenListen()
функцию после отображения модального. - Вы должны использовать
filter
вместоskipWhile
внутриcreateObserable
функции, потомуskipWhile
что разрешите каждое излучаемое значение после того, как оно впервые станет ложным, ноfilter
всегда будете фильтровать каждое излучаемое значение и разрешать только те, которые соответствуют заданному условию.
Возвращает наблюдаемое, которое пропускает все элементы, испускаемые наблюдаемым источником, до тех пор, пока указанное условие выполняется, но выдает все последующие элементы источника, как только условие становится ложным.
Фильтруйте элементы, испускаемые наблюдаемым источником, испуская только те, которые удовлетворяют указанному предикату.
Вот части вашего кода, которые следует изменить:
ngOnInit(): void {
this.inactivityListenService.initScreenListen();
this.inactivityListenService.observeable$.subscribe(() => {
// >>>>> You shouldn't unsubscribe here.
console.log('here show modal.....');
// >>>>> There is no need to call initScreenListen again here.
});
}
public createObserable(): void {
this.ngZone.runOutsideAngular(() => {
this.observeable$ = this.mergedObservable$.pipe(
switchMap(ev => interval(1000).pipe(take(this.inactivityTime))),
tap(value => {
this.isItTimeToShowPopUp(value);
}),
// >>>> use `filter` instead of `skipWhile`
filter(x => {
this.timeLapsedSinceInactivity = x;
// >>> reflect the condition after using `filter`
return x === this.inactivityTime - 1;
})
);
});
}
А вот и рабочий стекблитц
Ответ №2:
Простой способ сделать это-создать наблюдаемое, которое излучает всякий раз, когда после 5000 мс не было никаких «действий». debounceTime
Оператор отлично подойдет для этого:
export class InactivityListenService {
private activityTriggers$ = merge(
fromEvent(document, 'mousemove'),
fromEvent(document, 'keypress' ),
fromEvent(window, 'mousemove'),
);
public idle$: Observable<void> = this.activityTriggers$.pipe(
startWith(undefined),
debounceTime(IDLE_THRESHOLD),
mapTo(undefined)
);
}
Здесь мы объединяем все activityTriggers$
использование merge
, как вы уже делали.
Затем мы строим idle$
наблюдаемое на основе activityTriggers$
этого использования debounceTime
для предотвращения выбросов до тех пор, пока не пройдет указанный интервал времени с момента получения выброса «активности».
Здесь я использовал mapTo(undefined)
, так как излучение исходного события не кажется уместным (конечно, в этом нет необходимости).
Мы используем startWith
, чтобы обеспечить начальную эмиссию для «запуска таймера» в случае, если пользователь вообще ничего не делает.
Теперь вы можете просто подписаться idle$
и выполнять свою работу (режим отображения).
Вот рабочая демонстрация StackBlitz.