Я пытаюсь реализовать простую форму from/to/duration в rxjs для изучения.
Было бы намного проще просто использовать геттеры/сеттеры, но я хочу этому научиться.
Может ли кто-нибудь помочь мне найти простое решение с помощью 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;
}
}