ViewModel 2 - A new level of simplicity

They have nothing to do with each other. The first implementation of ViewModel (3 years ago?) was modeled after Knockout. That’s why properties are converted to functions (that’s how Knockout does it). I’ve continued the practice because it is very useful (you can toss properties/functions around, they can have properties of their own, etc.)

Even though ViewModel trims the fat out of pretty much all other MVVM implementations, I think the biggest difference is the way ViewModel approaches component communication. ViewModel’s approach is that every component should be in charge of its own state. That said, there are cases when two component need to share some kind of data. In those cases, instead of sharing the whole state of the application between all components or sending messages from one component to another, with ViewModel you literally share the one piece of state/property that needs to be shared between the two components and that’s it.

Great. Got it. Thanks.

Should the ViewModel way be considered the “best practice” way to develop Meteor applications?
Would it even be good if it was integrated into the core Meteor platform / framework and officially supported by MDG?
Or are there any cases in which ViewModel is not recommended?

1 Like

My personal view: If you stay with Blaze, then ViewModel should be “best”. But it’s too easy to make components/templates smart, such that they have too much “magic”. MDG prefers React because it encourages dumb components ( and of course the huge eco-system). That makes better sense in lager scale apps for less magic and easier testing. That’s why VM won’t be officially endorsed. Of course, VM components could be dumb, depending on how you make use of it.

2 Likes

I made a post on this focus with a suggestion of integrating ViewModel with Blaze once it fully transitions to being community driven.

I believe this will not only increase the exposure, but VM can truly make Blaze a real candidate for your views, as well as giving up potential for making VM function even smoother than it does now - syntax upgrades etc.

If you agree, please post on the topic I made =)

Today I have come to a conclusion that Viewmodel’s if\unless bindings are way more performant than Blaze’s native ones.
One can say that it is pretty obvious to anyone because Blaze does dom manipulation whereas VM only changes css display property.
Well, it wasn’t that obvious for me and I’m happy to finally realize that. My UI became significantly more performant.
I hope that opens someone’s else eyes as well :slight_smile:

I’m having a problem, wondering if anyone had similar issues:

I need a function that runs when the results of a subscription change. The project has been updates to use Materialize, which needs the javascript function for “selects” to be ran after every change.

I’m wondering best way to bind that, or the “observeChanges” function in ViewModel? I can’t seem to find any bindings related to subscription change.

To re-run a function if its dependencies change you can use an autorun:

Template.example.viewmodel({
  autorun: function() {
    let value = Collection.findOne().value;
    console.log( value );
  }
})

That said you rarely need to use autoruns. The golden rule is “don’t store a value which can be computed”.

I think what you’re trying to do is update a SELECT element from a collection. If so you have two choices, one is use Blaze’s each block using a function that returns a cursor (a find on a collection). The other choice is to use the options binding:

<template name="example">
  <label>Countries</label>
  <select {{b "options: countries, optionsValue: id, optionsText: name, value: selected"}} ></select>
  <label {{b "text: 'Selected id: ' + selected"}} ></label>
</template>
Template.example.viewmodel({
  selected: '',
  countries: function() {
    return Countries.find();
  }
});

Right now I am using the ViewModel functionality for selects, where…

partCategories: function() {
        return PartCategory.find();
    }

and the select:

<select class="form-control" {{b "options: partCategories, optionsValue: name, value: selectedPartCat, optionsText: name, defaultText: 'Choose a Part Category:', defaultValue: ''"}} ></select>

The problem is, Materialize’s select’s must be initialized with:

$('select').material_select();

So basically I need to run that after each time the collection updates.

I’ve tried a few things, but the problem is the only solutions I found that worked, would delete the contents of the select when I choose something in the select box.

This isn’t ViewModel specific problem (you’d have the same issue with any view layer). I haven’t used Material but assuming material_select() changes the html (as opposed to keeping the select element and its options), you have 2 options as I see it:

  1. Use an autorun to blow the element and recreate it.
  2. Write the html as material_select() would create in the first place (but looping through the options)

Some example pls? :wink:

I’m really excited by viewmodel, but the new syntax doesn’t work in Jade. Or at least, I can’t get it to work. Also if you try the new syntax in the html to jade compilers, it messes up

$b=“click : doSomething, value : someHelper” does the trick in dalgard:jade (that’s a fork of mquandalle:jade, not sure if it mqandalle merged that)

A lot of people use ViewModel with Jade. As @janmp mentioned, you can use dalgard:jade (the syntax is quite nice).

Regarding “don’t store a value which can be computed”, every so often someone will post a question regarding a view model that gets unwieldy. Almost always they would be watching changes in one or more values, computing another, and then storing the computed value in a property.

For example, if you have something like this:

Template.person.viewmodel({
  first: '',
  last: '',
  full: '',
  autorun() {
    this.full( this.first() + ' ' + this.last() );
  }
})

Do yourself a favor and cut the autorun:

Template.person.viewmodel({
  first: '',
  last: '',
  full() {
    return this.first() + ' ' + this.last();
  }
})

In fact, unless you have a really good reason, cut all autoruns from your view models. Chances are there’s a simpler way to achieve things without them.

2 Likes

I’ve been playing around with x3dom and ran into a problem:

With the meteor packages that are available on atmosphere (i’ve been using yottzumm:x3dom) comes a fix that let’s events properly bubble out of the x3dom canvas.

The only exception seems to be onoutputchange. That actually requires an old-fashioned attribute with a string that contains a function call (and the meteor fix actually implements that with an eval).

How can I plug my viewmodel helpers into this frigging onoutputchange?

<PositionInterpolator DEF="move" key="0 0.5 1" keyValue="0 -3 0  1 3 1  0 -3 0"
   onoutputchange="console.log(event)"></PositionInterpolator>

To try it out a full template with an animated sphere (in jade and coffeescript):

template(name="testOutputChange")
  x3d(width="600px" height="400px")
    scene
      transform(DEF="ball")
        shape
          appearance
            material(diffuseColor="1 0 0")
          sphere
      timeSensor(DEF="time" cycleInterval="2" loop="true")
      PositionInterpolator(DEF="move" key="0 0.5 1" keyValue="0 -3 0  1 3 1  0 -3 0"
        onoutputchange="console.log('the useless way that works', event)")
        //$b="outputchange : handleEvent") //this is what I would like to do, but it doesn't work
      Route(fromNode="time" fromField ="fraction_changed" toNode="move" toField="set_fraction")
      Route(fromNode="move" fromField ="value_changed" toNode="ball" toField="translation")
require "./testOutputChange.jade"

Template.testOutputChange.viewmodel
  handleEvent : (event) -> console.log "the way it should be", event
  onRendered : ->
    x3dom.reload()

The typical way to deal with this situation is to use the control’s built in events. Something like:

Template.example.viewmodel
  onRendered: ->
    x3dom.reload( { outputchange: => this.doSomething() } ) # made up syntax

If you can’t do that maybe you’ll have to get dirty and call the view model from the event string:

  PositionInterpolator( onoutputchange="ViewModel.findOne('testOutputChange').doSomething(event)" )

See https://viewmodel.org/docs/viewmodels#find

1 Like

ViewModel.find is working fine. Why didn’t I think of it? Thanks Manuel :slight_smile:

1 Like

Hi @manuel , great stuff here! I love the idea of simplifying the dev cycle while still keeping things sanely together.

I’m about to launch into a project and want to use ViewModel, but I can’t seem to find answers to these questions so that I can get some clarity on whether to keep going with ViewModel as the tool.

  1. Is ViewModel ready for production? ( will it break under stress or scaling tests? )
  2. Can google’s Material components be used without any foreseeable issues? (what’s the best Material components library to use? material.angularjs.org ? material-ui.com ? materializecss.com ? should it be Angular2-based?
  3. Where do you see ViewModel heading in the future ? How long can we rely on the current release to be “still working”?

Thanks for your time!

m

I can share a bit based on my experience:

  1. Yes. It’s production ready. I’m using it extensively. For a SPA with: 10+ pages of steps; each page has one or more dynamic forms; each form consist of 10+ fields; each field is customised VM based component wrapping other bootstrap based controls, plugins (datepicker, dropdown select etc…). The fields auto loads related data. Works great so far.
  2. VM is just on top of Blaze.

Hope that helps.

2 Likes