0

I am trying to write a program which automatically converts two units between each other and fills the other unit with the converted value i.e. a monthly salary to hourly salary converter.

To better demonstrate what I'm trying to do, here is a stripped version of my knockout model:

class Model {
    hourlyRate: KnockoutObservable<number>;
    monthlyRate: KnockoutObservable<number>;
    hoursPerWeek: KnockoutObservable<number>;

    constructor() {
        this.hourlyRate = ko.observable<number>();
        this.monthlyRate = ko.observable<number>();
        this.hoursPerWeek = ko.observable<number>(40);

        this.monthlyRate.subscribe((newValue: number) => {
            const hourlyRate = newValue * 3 / 13 / this.hoursPerWeek();
            this.hourlyRate(hourlyRate);
        });

        this.hourlyRate.subscribe((newValue: number) => {
            const monthlyRate = newValue * this.hoursPerWeek() * 13 / 3;
            this.monthlyRate(monthlyRate);
        });
    }
}

However, this results in a call stack exceeded exception (hourlyRate updates monthlyRate, which then updates hourlyRate, which in turn updates monthlyRate... indefinitely).

How do I prevent this from happening?

nbokmans
  • 5,492
  • 4
  • 35
  • 59
  • Check this [thread](https://stackoverflow.com/questions/17983118/change-observable-but-dont-notify-subscribers-in-knockout-js) I suspect you are going to need to refactor a couple of things in order to get this to work right. – Adam H Oct 23 '18 at 15:46

1 Answers1

3

I think this would be a great place to use writable computed observables.

Here's a snippet (apologies, I'm not well-versed in typescript):

var viewModel = function(){
  this.hourlyRate = ko.observable();
  this.hoursPerWeek = ko.observable(40);
  this.monthlyRate = ko.pureComputed({
    read: function(){
      return this.hourlyRate() * this.hoursPerWeek() *13/3;
    },
    write: function(newValue){
      const hourlyRate = newValue * 3 / 13 / this.hoursPerWeek();
      this.hourlyRate(hourlyRate);
    },
    owner: this
  });
};

ko.applyBindings(new viewModel());
label {
  display: inline-block;
  width: 140px;
  text-align: right;
}

.block {
  padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div class="block">
    <label>Hourly Rate:</label>
    <input type="number" data-bind="value: hourlyRate">
</div>
<div class="block">
    <label>Monthly Rate:</label>
    <input type="number" data-bind="value: monthlyRate">
</div>
Ray
  • 3,864
  • 7
  • 24
  • 36