ViewModel 2 - A new level of simplicity

Incidentally - I just saw your Babel transpiler React one - very cool!

What a coincidence, I work in the Mortgage industry =)

The key point is that you have circular references and thus ā€œsomethingā€™s got to giveā€. We deal with situations like this all the time. Hereā€™s a not so made up example:

You have 3 interdependent fields: Loan Amount, Total Years, and Monthly Payment (letā€™s forget interest for simplicityā€™s sake). And so the formula is:
Monthly Payment = Loan Amount / (TotalYears * 12)

Fair enough, the problem now is that any value affects the other so, again, ā€œsomethingā€™s got to giveā€. So we (a business analyst actually) are going to decree the following 2 things:

  1. The value entered by the user is always right and thus canā€™t change in our computations.
  2. Loan Amount is more important than Total Years, and Total Years is more important than Monthly Payment.

With that we can construct our component:

App({
  
  loanAmount: 200000,
  loanAmountFocus: false,
  
  totalYears: 30,
  totalYearsFocus: false,
  
  monthlyPayment: 555.56,
  monthlyPaymentFocus: false,

  autorun: [
    function() {
      // Watch for changes in Loan Amount
      const loanAmount = this.loanAmount();
      // Only do this if the user is changing Loan Amount
      if (!this.loanAmountFocus()) return;
      const totalYears = this.totalYears.value;
      // We "decree" that Total Years is more important than Monthly Payment
      // And thus we only update Monthly Payment based on Total Years
      this.monthlyPayment( (loanAmount / ( totalYears * 12 )).toFixed(2) );
    },
    function() {
      // Watch for changes in Total Years
      const totalYears = this.totalYears();
      // Only do this if the user is changing Total Years
      if (!this.totalYearsFocus()) return;
      const loanAmount = this.loanAmount.value;
      // We "decree" that Loan Amount is more important than Monthly Payment
      // And thus we only update Monthly Payment based on Loan Amount
      this.monthlyPayment( (loanAmount / ( totalYears * 12 )).toFixed(2) );
    },
    function() {
      // Watch for changes in Monthly Payment
      const monthlyPayment = this.monthlyPayment();
      // Only do this if the user is changing Monthly Payment
      if (!this.monthlyPaymentFocus()) return;
      const loanAmount = this.loanAmount.value;
      // We "decree" that Loan Amount is more important than Total Years
      // And thus we only update Total Years based on Loan Amount
      this.totalYears( (loanAmount / ( monthlyPayment * 12 )).toFixed(2) );
    }
  ],
  render(){
    <div>
      Loan Amount: <input b="value: loanAmount, focus: loanAmountFocus"/><br />
      Total Years: <input b="value: totalYears, focus: totalYearsFocus"/><br />
      Monthly Payment: <input b="value: monthlyPayment, focus: monthlyPaymentFocus"/><br />
      <button b="click: reset">Reset Values</button>
    </div>
  }
});

Hereā€™s the repro: https://github.com/ManuelDeLeon/mortgage-calculator

In our case we offload such computations to a calc engine (a service/mixin). We donā€™t do it in the component itself but the logic is the same. We just pass the calc engine the value the user is working on and ā€œfreezeā€ it.

1 Like

Thanks! I absolutely love working with viewmodel-react. Even after months of working with it, I honestly smile when I see my code. Thereā€™s something right about seeing ā€œjust my codeā€. The main page of viewmodel.org has the React documentation.

3 Likes

In your case I would not bind input fields to state directly and eliminate a notion of derived property.
In your case properties should not ā€˜observeā€™ anything and simply hold the state.
I would submit input events to controller or dispatcher that would detect which input was modified and what consequences must follow.
Based on those colculations it should submit new values to state properties. Via ā€˜actionsā€™ or whatever.
Whatā€™s good with this approach is that you can actually determine what was the cause of state change via logging dispatcher actions.

Could you expand on that

and that

Our system has a loan object which is basically a bag of fields related to a mortgage. It has information about borrowers, properties, prices, interest rates, etc. As you mentioned, we canā€™t just use computed fields for everything because a lot of the fields are interdependent.

The calc engine is basically a set of functions that contain all the business logic regarding how fields are related to one another. At risk of oversimplification, you give it a loan object plus the new field that needs to change, and it will give you a new loan object based on the existing rules.

By freeze I mean make sure the calc engine doesnā€™t change the value the user just entered. We use the term ā€œlock a valueā€ too. We also have the ability to freeze/lock multiple values at the same time (while the calc engine does its thing). Again itā€™s all based on business rules.

Still on the Blaze ViewModel here for a while! Thanks for your hard work.

3 Likes

Incidentally, I use just the same approach.
I store all the business logic that is used by several components in the global Logic object which is itself a set of pure functions.
If business logic is unique to the component though - I do keep it inside the component.

Whatā€™s the best way to use a Subscription Cache/Manager with ViewModel, especially in a Meteor 1.3+ project? At this point, weā€™re only looking to implement a global cache. Any pointers to how to go about this would be really helpful. Thanks.

Please clarify what you mean with ā€œSubscription Cache/Managerā€ and ā€œglobal cacheā€. The first thing that comes to mind is redux.

Iā€™m still using Blaze atm, so Iā€™m trying to understand how to use ViewModel with something like Subs-Manager(https://github.com/kadirahq/subs-manager) or Subs-Cache (https://github.com/ccorcos/meteor-subs-cache) to cache the results of subscriptions to Meteor publications.

By global cache, Iā€™m specifically referring to the note mentioned in the Subs-Manager documentation (https://github.com/kadirahq/subs-manager#patterns-for-using-subsmanager) about how it can be used. In this case, Iā€™m just looking to instantiate a single Single subscription manager for all the subscriptions Iā€™m planning to cache, rather than multiple Subscription managers.

Iā€™m trying to improve the responsiveness of the app when moving between a list view and a detail view and back, and waiting for the list view to re-query and then render the list, which is a noticeable delay. For some reason, my brainā€™s freezing up in terms of the scoping and figuring out how exactly to instantiate and use this within ViewModel bindings.

Hope this clarifies what Iā€™m asking.

The process is pretty much the same as without VM. I tend to be more hands on when it comes to subscriptions. If I know a child component is likely to be used then I load the data in the parent. In your case you can just create a single subscription manager in a file (exporting it), and then import it wherever you need to use it. Maybe Iā€™m missing something.

Got it. Thanks. For some reason, I overlooked this.

Sorry for the noob question. Still getting to grips with Meteor 1.3+ patterns.

I agree.

This is an amazing piece of work, and definitely the most elegant viewmodel layer iā€™ve ever seen and ive been developing js for 7+ years and php for 3-4 before thatā€¦

Manuel is a boss.

4 Likes