Я не смотрел на вашу конкретную проблему. Но вот очень простая реализация таймера обратного отсчета, который сбрасывается каждый раз, когда пользователь щелкает или перемещает мышь в любом месте приложения.
import { Component, OnInit } from "@angular/core";
import { timer, Observable, fromEvent, merge, Subject } from "rxjs";
import { startWith, switchMap, finalize, take, map } from "rxjs/operators";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
countDown$: Observable<any>;
ngOnInit() {
this.countDown$ = merge(
fromEvent(document, "mouseup"),
fromEvent(document, "mousemove")
).pipe(
startWith(false),
switchMap(_ => {
let counter = 61;
return timer(0, 1000).pipe(
take(counter),
map(_ => --counter),
tap(null, null, () => {
console.log("countdown complete");
// redirect to login page
})
);
})
);
}
}
Рабочий пример: Stackblitzс>
Рабочий пример: Stackblitz
Авария
fromEvent
— используется для захвата событий mousedown
и mouseup
. Вы можете настроить его для определенной части экрана, используя эталонные переменные шаблона Angular, или добавить/удалить дополнительные события.
merge
— используется для объединения нескольких событий в одно событие
startWith
— Используется для генерации случайной переменной перед генерацией любого события. Используется для запуска обратного отсчета при запуске приложения.
switchMap
— оператор отображения более высокого порядка, используемый для отмены текущего внутреннего наблюдаемого (здесь timer
), когда испускается исходный наблюдаемый (здесь merge
).
timer
— Используется для создания значения каждую секунду.
map
— преобразовать значение из timer
в значение нашего таймера обратного отсчета.
finalize
— будет запущено только после завершения timer
. Другими словами, пока исходные наблюдаемые объекты продолжают излучать в течение 60 секунд, они не будут запущены. Таким образом, вы можете включить сюда механизм маршрутизации.
Обновление – finalize
не работает должным образом
Похоже, что каждое событие в merge
, возможно, просачивается в оператор finalize
, поскольку каждое из них по отдельности завершается до изменения наблюдаемого из switchMap
. Я заменил его полным блоком оператора tap
, который работает, как и ожидалось. Приведенное выше объяснение для finalize
должно по-прежнему работать, но вместо этого заменено на tap
.
tap
— обратный вызов complete
будет запущен только после завершения timer
. Другими словами, пока исходные наблюдаемые объекты продолжают излучать в течение 60 секунд, они не будут запущены. Таким образом, вы можете включить сюда механизм маршрутизации.
Обновление: используйте обратный отсчет со службой аутентификации
Я полностью обновил иллюстрацию, чтобы она работала в тандеме со службой аутентификации, которая имитирует HTTP-вызовы для действий входа и выхода.
По вашим вопросам в разделе комментариев
- Является ли хорошей практикой определение переменной
countDown$
как типа any
?
Короче говоря, нет. Вместо этого вы можете использовать Observable<number>
, так как он содержит число, испускаемое из наблюдаемого.
- Также я получаю следующее предупреждение об устаревании от оператора крана:
tap is deprecated: Use an observer instead of a complete callback (deprecation)
Как исправить устаревание?
tap
не считается устаревшим. Скорее всего, речь идет о перегрузке функции, которая устарела. Вместо этого вы можете использовать объект со свойствами next
, error
и complete
.
- Кстати, мне нужно запускать таймер только после входа в систему, как мне это сделать? В моем коде я просто использую вызов
startTimer()
в службе аутентификации.
Надеюсь, обновленной иллюстрации будет достаточно.
app.component.ts
import { Component, OnDestroy } from "@angular/core";
import { fromEvent, merge, Subject } from "rxjs";
import { switchMap, takeUntil } from "rxjs/operators";
import { AuthenticationService } from "./authentication.service";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnDestroy {
closed$ = new Subject<any>();
countDown: number;
constructor(private authService: AuthenticationService) {}
login() {
this.authService
.login()
.pipe(
switchMap(_ =>
merge(
fromEvent(document, "mouseup"), // <-- replace these with user events to reset timer
fromEvent(document, "mousemove")
)
),
this.authService.countdownTimer(),
takeUntil(this.closed$)
)
.subscribe(countDown => (this.countDown = countDown));
}
logout() {
this.authService
.logout()
.pipe(takeUntil(this.closed$))
.subscribe();
}
ngOnDestroy() {
this.closed$.next();
}
}
app.component.html
<ng-container *ngIf="(authService.loginStatus$ | async); else loggedOut">
<p>User is logged in. Automatic logout in: {{ countDown }}s</p>
<br />
<button (mouseup)="logout()">Logout</button>
</ng-container>
<ng-template #loggedOut>
<p>User is logged out.</p>
<br />
<button (mouseup)="login()">Login</button>
</ng-template>
authentication.service.ts
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { timer, Observable, Subject, BehaviorSubject } from "rxjs";
import {
tap,
startWith,
switchMap,
takeUntil,
take,
map
} from "rxjs/operators";
export const LOGIN_TIME = 6;
@Injectable()
export class AuthenticationService {
public loginStatusSrc = new BehaviorSubject<boolean>(false);
public stopTimerSrc = new Subject<any>();
public loginStatus$ = this.loginStatusSrc.asObservable();
public stopTimer$ = this.stopTimerSrc.asObservable();
constructor(private http: HttpClient) {}
login(): Observable<any> {
// simulate a login HTTP call
return this.http.get("https://jsonplaceholder.typicode.com/users/1").pipe(
tap({
next: () => this.loginStatusSrc.next(true),
error: () => this.loginStatusSrc.next(false)
})
);
}
logout() {
// simulate a logout HTTP call
return this.http.get("https://jsonplaceholder.typicode.com/users/1").pipe(
tap({
next: () => {
this.loginStatusSrc.next(false); // <-- hide timer
this.stopTimerSrc.next(); // <-- stop timer running in background
},
error: () => this.loginStatusSrc.next(false)
})
);
}
countdownTimer() {
return <T>(source: Observable<T>) => {
return source.pipe(
startWith(null),
switchMap(_ => {
let counter = LOGIN_TIME;
return timer(0, 1000).pipe(
take(counter),
map(_ => --counter),
tap({
next: null,
error: null,
complete: () => {
this.stopTimerSrc.next(); // <-- stop timer in background
this.loginStatusSrc.next(false);
console.log("Countdown complete. Rerouting to login page...");
// redirect to login page
}
})
);
}),
takeUntil(this.stopTimer$)
);
};
}
}
Вот руководство, которое я использовал для создания пользовательского оператора RxJS countdownTimer()
: https://netbasal.com/creating-custom-operators-in-rxjs-32f052d69457
Рабочий пример: Stackblitz
person
Michael D
schedule
15.10.2020