rxjs-简单的从/开始/持续时间的问题

时间:2019-05-28 11:09:02

标签: angular rxjs

我正在尝试在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;
  }
}

1 个答案:

答案 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个流,我们现在避免了您提到的无休止循环。

我希望这是合理的。为简单起见,我省去了您使用的时间格式。