我正在尝试在rxjs中实现一个简单的从/到/持续时间形式来学习。
仅使用getter / setter方法会简单得多,但我想学习它。
有人可以帮我用rxjs找到一个简单的解决方案来解决这个问题吗?
From : Start time of day
To : End time of day
Time : Duration between to and from
To
,则Time
应该更新,除非From
为空Time
,则To
应该更新,除非From
为空我相信我当前的实现存在很多问题,因为它会导致无限循环。另外,由于我订阅了很多次,所以似乎以递延的方式收到了可观察的值。
import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { distinct, distinctUntilChanged, filter, map } from 'rxjs/operators';
export class Clock implements OnInit {
fromSubject: BehaviorSubject<string> = new BehaviorSubject('');
toSubject: BehaviorSubject<string> = new BehaviorSubject('');
timeSubject: BehaviorSubject<string> = new BehaviorSubject('');
from$: Observable<string>;
to$: Observable<string>;
time$: Observable<string>;
seconds$: BehaviorSubject<number> = new BehaviorSubject(0);
_from: string;
_to: string;
_time: string;
get from(): string {
return this._from;
}
set from(value: string) {
this.fromSubject.next(value);
}
get to(): string {
return this._to;
}
set to(value: string) {
this.toSubject.next(value);
}
get time(): string {
return this._time;
}
set time(value: string) {
this.timeSubject.next(value);
}
ngOnInit() {
this.from$ = this.fromSubject.pipe(
distinctUntilChanged(),
map(from => {
if (!from) {
return from;
} else if (!isNaN(+from)) {
return from + ':00';
} else {
const seconds = this.convertTimeToSeconds(from);
return this.convertSecondsToTime(seconds);
}
})
);
this.to$ = this.toSubject.pipe(
distinctUntilChanged(),
map(to => {
if (!to) {
return to;
} else if (!isNaN(+to)) {
return to + ':00';
} else {
const seconds = this.convertTimeToSeconds(to);
const txt = this.convertSecondsToTime(seconds);
return txt;
}
})
);
this.time$ = this.timeSubject.pipe(
distinctUntilChanged(),
map(time => {
if (!time) {
return time;
} else if (!isNaN(+time)) {
return time + 'h';
} else {
const seconds = this.convertDurationToSeconds(time);
console.log('TimeSeconds ' + seconds);
return this.convertSecondsToDuration(seconds);
}
})
);
this.from$.subscribe(f => {
this._from = f;
});
this.to$.subscribe(t => {
this._to = t;
});
this.time$.subscribe(t => {
this._time = t;
const seconds = this.convertDurationToSeconds(t);
this.seconds$.next(seconds);
});
combineLatest(this.from$, this.seconds$)
.pipe(filter(([from, seconds]) => !!from && !!seconds))
.subscribe(([from, seconds]) => {
if (!from) {
return;
}
const fromHours = this.getHours(from);
const fromMins = this.getMinutes(from);
const fromSeconds = fromHours * 3600 + fromMins * 60;
const toSeconds = fromSeconds + seconds;
const to = this.convertSecondsToTime(toSeconds);
const time = this.convertSecondsToDuration(toSeconds - fromSeconds);
this.toSubject.next(to);
this.timeSubject.next(time);
});
combineLatest(this.from$, this.to$).subscribe(([from, to]) => {
if (!from || !to) {
return;
}
const fromHours = this.getHours(from);
const toHours = this.getHours(to);
const fromMins = this.getMinutes(from);
const toMins = this.getMinutes(to);
const seconds = (toHours - fromHours) * 3600 + (toMins - fromMins) * 60;
this.seconds$.next(seconds);
});
const now = new Date();
const hours = now.getHours();
let minutes = now.getMinutes();
minutes = minutes - (minutes % 15);
this.to = hours + ':' + minutes;
this.from = '8:00';
this.to = '10:00';
}
convertDurationToSeconds = str => {
let seconds = 0;
const hours = str.match(/^(\d+)\s*(?:h|hours)/);
const minutes = str.match(/^(?:\d+h)?(\d+)\s*(?:(?:m)|(?:min))?$/);
if (hours) {
seconds += parseInt(hours[1], 10) * 3600;
}
if (minutes) {
seconds += parseInt(minutes[1], 10) * 60;
}
return seconds;
}
convertTimeToSeconds = str => {
let seconds = 0;
const hours = this.getHours(str);
const minutes = this.getMinutes(str);
if (hours) {
seconds += hours * 3600;
}
if (minutes) {
seconds += minutes * 60;
}
return seconds;
}
convertSecondsToDuration(seconds: number) {
const durationHours = Math.floor(seconds / 3600);
const durationMinutes = Math.floor((seconds - durationHours * 3600) / 60);
let durationStr = '';
if (durationHours > 0) {
durationStr = durationHours + 'h';
}
if (durationMinutes > 0) {
durationStr = durationStr + durationMinutes + 'm';
}
if (!durationStr) {
durationStr = '0h';
}
console.log('durationStr ' + durationStr + ' seconds : ' + seconds);
return durationStr;
}
convertSecondsToTime(seconds: number) {
const durationHours = Math.floor(seconds / 3600);
const durationMinutes = Math.floor((seconds - durationHours * 3600) / 60);
const durationHoursStr = durationHours.toString();
const durationMinutesStr =
durationMinutes < 10 ? '0' + durationMinutes : durationMinutes;
const timeStr = durationHoursStr + ':' + durationMinutesStr;
console.log(seconds, durationHours, durationMinutes, timeStr);
return timeStr;
}
getHours = str => {
const hours = str.match(/^((?:(2[0-3])|(1[0-9])|0?[0-9])):(?:[0-5][0-9])/);
if (hours) {
return parseInt(hours[1], 10);
}
return 0;
}
getMinutes = str => {
const minutes = str.match(
/^(?:(?:2[0-3])|(?:1[0-9])|0?[0-9]):((?:[0-5][0-9])|[0-9])/
);
if (minutes) {
return parseInt(minutes[1], 10);
}
return 0;
}
}
答案 0 :(得分:0)
我确实不得不说,作为RxJS的“简单示例”,您确定选择了一个带有一些尴尬业务逻辑的示例。 :)(例如,另一种极端情况是,如果用户将“从”更改为时间,并且已经更改了时间,时间是否保持不变并且“至”是否进行了更新?)。
在我绝对必须使用它们之前,我尝试避免BehaviorSubjects,所以现在让我们从Subjects开始,看看是否可行:
from = new Subject<string>();
to = new Subject<string>();
time = new Subject<string>();
from
仅从其输入中设置(我猜是UI)。因此,我们将其定义为无需额外处理的Observable:
from$ = this.from.asObservable()
to
是从其输入或“ timeFrom”流中设置的(抱歉,无法使用更好的名称):
timeFrom$ = combineLatest(this.time, this.from).pipe(
map([time, from] => time - from) // I'm leaving out formatting in this example
)
CombineLatest的优点在于它满足您的要求,即如果“ from”具有值,则仅使用timeFrom,因为除非两个流都已发出,否则combineLatest不会发出。因此我们的to$
流变为:
to$ = merge(
this.to,
this.timeFrom$
)
类似地,我们可以将时间定义为来自其输入或“ toFrom”流的时间:
toFrom$ = combineLatest(this.to, this.from).pipe(
map([to, from] => from - to)
)
结果是:
time$ = merge(
this.time,
this.toFrom$
)
通过将问题分成5个流而不是3个流,我们现在避免了您提到的无休止循环。
我希望这是合理的。为简单起见,我省去了您使用的时间格式。