rxjs - ปัญหาง่าย ๆ จาก / เริ่มต้น / ระยะเวลา

ฉันกำลังพยายามใช้แบบฟอร์ม from / to / Duration แบบง่าย ๆ ใน rxjs เพื่อเรียนรู้

มันจะง่ายกว่ามากถ้าใช้ getter / setters แต่ฉันต้องการเรียนรู้มัน

ใครสามารถช่วยฉันค้นหาวิธีแก้ไขปัญหาง่ายๆ ด้วย 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;
  }
}


person Ludovic C    schedule 28.05.2019    source แหล่งที่มา


คำตอบ (1)


ฉันต้องบอกว่าในฐานะ "ตัวอย่างง่ายๆ" ของ RxJS คุณแน่ใจว่าเลือกอันที่มีตรรกะทางธุรกิจที่น่าอึดอัดใจ :) (เช่น กรณี Edge อีกกรณีหนึ่งคือ หากผู้ใช้เปลี่ยน "จาก" และเปลี่ยนเวลาไปแล้ว เวลาจะคงเดิมและ "เป็น" อัปเดตหรือไม่)

ฉันพยายามหลีกเลี่ยง 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 คือมันตอบสนองความต้องการของคุณที่เราจะใช้เฉพาะ timeFrom ถ้า "from" มีค่าเท่านั้น เนื่องจาก 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 สตรีม ตอนนี้เราได้หลีกเลี่ยงการวนซ้ำไม่รู้จบที่คุณพูดถึงแล้ว

ฉันหวังว่านี่จะสมเหตุสมผล เพื่อความง่าย ฉันจึงละเว้นการจัดรูปแบบเวลาที่คุณมีอยู่

person Jesse    schedule 28.05.2019