rxjs - masalah sederhana dari / mulai / durasi

Saya mencoba menerapkan formulir sederhana dari / ke / durasi di rxjs untuk dipelajari.

Akan lebih mudah jika hanya menggunakan pengambil/penyetel tetapi saya ingin mempelajarinya.

Dapatkah seseorang membantu saya menemukan solusi sederhana dengan rxjs untuk mengatasi masalah ini?

From : Start time of day
To : End time of day
Time : Duration between to and from
  • Jika pengguna mengubah To, Time harus memperbarui, kecuali From kosong
  • Jika pengguna mengubah Time, To harus memperbarui, kecuali From kosong

Saya yakin implementasi saya saat ini memiliki banyak masalah karena menghasilkan loop tak terbatas. Selain itu, sepertinya karena saya berlangganan berkali-kali, saya menerima nilai yang dapat diamati secara tertunda.

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 sumber


Jawaban (1)


Saya harus mengatakan, sebagai "contoh sederhana" dari RxJS Anda tentu memilih satu dengan logika bisnis yang canggung. :) (Misalnya, kasus tepi lainnya adalah jika pengguna mengubah "dari" dan telah mengubah waktu, apakah waktu tetap sama dan "ke" diperbarui?).

Saya mencoba menghindari BehaviorSubjects sampai saya benar-benar harus menggunakannya, jadi untuk sekarang mari kita mulai dengan Subjek dan lihat apakah itu berhasil:

from = new Subject<string>();
to = new Subject<string>();
time = new Subject<string>();

from hanya disetel dari inputnya (saya kira UI). Jadi mari kita definisikan saja sebagai Observable tanpa penanganan tambahan:

from$ = this.from.asObservable()

to disetel dari masukannya atau dari aliran "timeFrom" (maaf, tidak dapat menemukan nama yang lebih baik):

timeFrom$ = combineLatest(this.time, this.from).pipe(
    map([time, from] => time - from) // I'm leaving out formatting in this example
)

Keunggulan dari kombinasiLatest adalah memenuhi persyaratan Anda bahwa kami hanya menggunakan timeFrom jika "dari" memiliki nilai, karena kombinasiLatest tidak memancarkan kecuali kedua aliran telah dipancarkan. Jadi aliran to$ kita menjadi:

to$ = merge(
    this.to,
    this.timeFrom$
)

Demikian pula, kita dapat mendefinisikan waktu yang berasal dari inputnya atau aliran "toFrom":

toFrom$ = combineLatest(this.to, this.from).pipe(
    map([to, from] => from - to)
)

Dan itu menghasilkan:

time$ = merge(
    this.time,
    this.toFrom$
)

Dengan memecah masalah menjadi 5 aliran, bukan 3, kini kami menghindari perulangan tanpa akhir yang Anda sebutkan.

Saya berharap ini masuk akal. Untuk mempermudah, saya mengabaikan format waktu yang Anda miliki.

person Jesse    schedule 28.05.2019