Angular 尝试实现计时器时服务未定义
Angular Service is undefined when trying to implement a timer
我正在尝试完成以下操作:对于我的网络应用程序,我尝试实现一个计时器,当用户显示 activity(意味着触发点击、按下或滚动事件时,该计时器会自动重置回 60 秒).我将 Observables 用于事件流。所有这一切都发生在 你登录我的网络应用程序之后。
如果计时器用完(从 60 秒到 0),用户应该简单地被重定向到后端 URL,这将删除 cookie 并重定向回前端的登录屏幕。现在一切都很好,花花公子,超时有效,但是当我单击某些内容时,滚动或单击键盘上的某些内容时没有任何反应。
我还想要一个检测鼠标移动的事件流,我该如何使用 RxJS 来实现?
我的代码:
数据服务:
// login timer
public timeLeft = 60;
public interval: number;
constructor(private http: HttpClient, public datepipe: DatePipe, private cookieService: CookieService) {
}
public startTimer() {
this.interval = setInterval(() => {
if(this.timeLeft > 0) {
this.timeLeft--;
} else {
// FIXME HTTPS implementation
window.location.href = 'http://example.com:8081/logout';
clearInterval(this.interval);
this.timeLeft = 60;
}
}, 1000);
}
public resetTimer() {
console.log('Juup');
clearInterval(this.interval);
console.log(this.timeLeft);
//this.timeLeft = 60;
//this.startTimer();
/* let time;
clearTimeout(time);
time = setTimeout(this.logout, 3000); */
}
登录逻辑:
return this.http.post<any>(`${environment.apiUrl}/api/login`, { email, password }, {withCredentials: true})
.pipe(map(user => {
// login logic here
// if everything is ok then
this.DataService.startTimer();
app.component.ts:
constructor(public authenticationService: AuthenticationService, public DataService: GlobalDataService,
private router: Router) {
const source1$ = fromEvent(document, 'click');
const source2$ = fromEvent(document, 'keydown');
const source3$ = fromEvent(document, 'scroll');
const sources$ = merge (
source1$,
source2$,
source3$
);
// map to string with given event timestamp
const example = sources$.pipe(map(event => `Event time: ${event.timeStamp}`));
// output (example): 'Event time: 7276.390000000001'
//
// const subscribe = sources$.subscribe(val => console.log(val));
const subscribe = sources$.subscribe(this.DataService.resetTimer);
interval: number;
subscribeTimer: any;
observableTimer() {
const source = timer(1000, 2000);
const abc = source.subscribe(val => {
console.log(val, '-');
this.subscribeTimer = this.DataService.timeLeft - val;
});
}
我的app.component.html:<p>{{this.DataService.timeLeft}}</p>
我的错误:
None.
杂项。控制台输出:
console.log 打印一次 Juup,然后为 timeLeft 打印出 undefined。每次我点击它都会发生。
我没有调查你的具体问题。但这是一个非常基本的倒计时计时器实现,每次用户在应用程序中的任何位置单击或移动鼠标时都会重置。
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 模板参考变量或 add/remove 附加事件将其调整到屏幕的特定部分。
merge
- 用于将多个事件合并为一个事件
startWith
- 用于在任何事件发出之前发出一个随机变量。用于在应用启动时启动倒计时。
switchMap
- 用于在源可观察对象(merge
此处)发出时取消当前内部可观察对象(此处的 timer
)的高阶映射运算符。
timer
- 用于每秒发出一个值。
map
- 将值从 timer
转换为我们的倒数计时器值。
finalize
- 仅在 timer
完成时触发。换句话说,只要 source observables 在 60 秒内持续发射,它就不会被触发。所以你可以在这里包含路由机制。
更新 - finalize
未按预期工作
merge
中的每个事件似乎都可能渗透到 finalize
运算符中,因为它们中的每一个似乎都在 switchMap
的修改后的可观察对象之前完成。我已将其替换为 tap
运算符的完整块,该块按预期工作。上面对 finalize
的解释应该仍然有效,但用 tap
代替
tap
- 它的 complete
回调只会在 timer
完成时触发。换句话说,只要 source observables 在 60 秒内持续发射,它就不会被触发。所以你可以在这里包含路由机制。
更新:将倒计时与身份验证服务结合使用
我已经完全更新了插图,以便与模拟 HTTP 登录和注销操作调用的身份验证服务协同工作。
关于评论区的问题
- 将
countDown$
变量定义为 any
类型是一个好习惯吗?
简而言之,没有。您可以改用 Observable<number>
,因为它包含从可观察对象发出的数字。
- 我还从 tap 操作员处收到以下弃用警告:
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
我正在尝试完成以下操作:对于我的网络应用程序,我尝试实现一个计时器,当用户显示 activity(意味着触发点击、按下或滚动事件时,该计时器会自动重置回 60 秒).我将 Observables 用于事件流。所有这一切都发生在 你登录我的网络应用程序之后。
如果计时器用完(从 60 秒到 0),用户应该简单地被重定向到后端 URL,这将删除 cookie 并重定向回前端的登录屏幕。现在一切都很好,花花公子,超时有效,但是当我单击某些内容时,滚动或单击键盘上的某些内容时没有任何反应。 我还想要一个检测鼠标移动的事件流,我该如何使用 RxJS 来实现?
我的代码:
数据服务:
// login timer
public timeLeft = 60;
public interval: number;
constructor(private http: HttpClient, public datepipe: DatePipe, private cookieService: CookieService) {
}
public startTimer() {
this.interval = setInterval(() => {
if(this.timeLeft > 0) {
this.timeLeft--;
} else {
// FIXME HTTPS implementation
window.location.href = 'http://example.com:8081/logout';
clearInterval(this.interval);
this.timeLeft = 60;
}
}, 1000);
}
public resetTimer() {
console.log('Juup');
clearInterval(this.interval);
console.log(this.timeLeft);
//this.timeLeft = 60;
//this.startTimer();
/* let time;
clearTimeout(time);
time = setTimeout(this.logout, 3000); */
}
登录逻辑:
return this.http.post<any>(`${environment.apiUrl}/api/login`, { email, password }, {withCredentials: true})
.pipe(map(user => {
// login logic here
// if everything is ok then
this.DataService.startTimer();
app.component.ts:
constructor(public authenticationService: AuthenticationService, public DataService: GlobalDataService,
private router: Router) {
const source1$ = fromEvent(document, 'click');
const source2$ = fromEvent(document, 'keydown');
const source3$ = fromEvent(document, 'scroll');
const sources$ = merge (
source1$,
source2$,
source3$
);
// map to string with given event timestamp
const example = sources$.pipe(map(event => `Event time: ${event.timeStamp}`));
// output (example): 'Event time: 7276.390000000001'
//
// const subscribe = sources$.subscribe(val => console.log(val));
const subscribe = sources$.subscribe(this.DataService.resetTimer);
interval: number;
subscribeTimer: any;
observableTimer() {
const source = timer(1000, 2000);
const abc = source.subscribe(val => {
console.log(val, '-');
this.subscribeTimer = this.DataService.timeLeft - val;
});
}
我的app.component.html:<p>{{this.DataService.timeLeft}}</p>
我的错误:
None.
杂项。控制台输出:
console.log 打印一次 Juup,然后为 timeLeft 打印出 undefined。每次我点击它都会发生。
我没有调查你的具体问题。但这是一个非常基本的倒计时计时器实现,每次用户在应用程序中的任何位置单击或移动鼠标时都会重置。
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 模板参考变量或 add/remove 附加事件将其调整到屏幕的特定部分。merge
- 用于将多个事件合并为一个事件startWith
- 用于在任何事件发出之前发出一个随机变量。用于在应用启动时启动倒计时。switchMap
- 用于在源可观察对象(merge
此处)发出时取消当前内部可观察对象(此处的timer
)的高阶映射运算符。timer
- 用于每秒发出一个值。map
- 将值从timer
转换为我们的倒数计时器值。finalize
- 仅在timer
完成时触发。换句话说,只要 source observables 在 60 秒内持续发射,它就不会被触发。所以你可以在这里包含路由机制。
更新 - finalize
未按预期工作
merge
中的每个事件似乎都可能渗透到 finalize
运算符中,因为它们中的每一个似乎都在 switchMap
的修改后的可观察对象之前完成。我已将其替换为 tap
运算符的完整块,该块按预期工作。上面对 finalize
的解释应该仍然有效,但用 tap
代替
tap
- 它的complete
回调只会在timer
完成时触发。换句话说,只要 source observables 在 60 秒内持续发射,它就不会被触发。所以你可以在这里包含路由机制。
更新:将倒计时与身份验证服务结合使用
我已经完全更新了插图,以便与模拟 HTTP 登录和注销操作调用的身份验证服务协同工作。
关于评论区的问题
- 将
countDown$
变量定义为any
类型是一个好习惯吗?
简而言之,没有。您可以改用 Observable<number>
,因为它包含从可观察对象发出的数字。
- 我还从 tap 操作员处收到以下弃用警告:
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