Logo
ManuelSchoebel

Use Deps Dependency instead of Session if you can

Meteors Session is a reactive dictionary in that you can store key value pairs and access from anywhere on client side of your application. Because of the reactiveness, the UI automatically rerenders the parts that depend on a specific key if that key changes. Wow, that's amazing - or is it?

Often times when you get something great you tend to use it a lot and i was using the reactivity of the Session a lot, too. But there is a downside to that and luckily you there is a better way to handle it.

The downside of the globally available Session is, that you put much more stuff in it than you have to, simply because you can and it is so easy. Think of a tiny ui-element that hast two different states. In this example we have a little purchase-form where you can buy credits. You can type in the credit-amount, your name and your address. If you press on 'continue' the ui-element will show the confirmation of your input. This looks like this:

Meteorjs Deps.Dependency example



You could save the state into the Session and when it changes your template would render the correct html. But why would your whole application have to know that the state of a tiny ui-element is 0? No one cares but this tiny ui-element. With the Session it is similar to the global namespace, I would try not to pollute it with lots of variables. Of course you could remove all keys and clean the Session up on every page change, but this is also really painful.

Instead I suggest you create a helper class for every 'main' template. This helper gets instantiated on the template create and is available only in the file of the template manager. This helper then hast reactive variables itself. Let's have a look.

At first I show you the folder structure of this little example:

client/
client/views/
client/views/purchase/credit_purchase.html
client/views/purchase/credit_purchase.coffee
client/views/home/home.html



The home.html contains only one template that does nothing but integrating the creditPurchase template:

<head>
  <title>deps_dependency</title>
</head>
 
<body>
  {{> creditPurchase}}
</body>

Then we have the creditPurchase Template:

<template name="creditPurchase">
  <div class="container">
    <div class="row">
      <div class="col-xs-12">
        {{#if stateIsOrder}}
          {{> orderForm}}
        {{else}}
          {{> orderConfirmation}}
        {{/if}}
      </div>
    </div>
  </div>
</template>


It checks the state with {{#if stateIsOrder}} and decides what to render. Obviously this has to be the reactive part. The other two templates do nothing to special:

<template name="orderForm">
  <form>
    <div class="form-group">
      <label for="amountInt">Net Amount</label>
      <input type="text" class="form-control" id="amountInt" value="{{amount}}">
    </div>
    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" &nbsp;class="form-control" id="name" value="{{name}}">
    </div>
    <div class="form-group">
      <label for="address">Address</label>
      <textarea id="address" rows="3">{{address}}</textarea>
    </div>
    <button type="submit" class="btn btn-default">Continue</button>
  </form>
</template>
 
 
<template name="orderConfirmation">
  <p>Amount: {{amount}}</p>
  <p>Vat: {{vat}}</p>
  <p>Total: {{total}}</p>
  <p>Address:</p>
  <p>{{address}}</p>
  <button class="btn btn-default edit">Edit</button>
</template>

In the template manager credit_purchase.coffee we create the local helper class and add the reactive variable we need. (Sorry for coffeescript. If you would like to see the javascript, copy and paste it at js2coffee.org, they can also do coffee 2 js!)

class CreditPurchase
  @ORDER_STATE: 0
 
 
  _deps: {}
    amountInt: 0
    state: 0
 
    constructor: () ->
      # create a Deps.Dependency for each reactive variable
      @_deps['state'] = new Deps.Dependency
 
    # reactive getters and setters
    getState: () ->
      @_deps['state'].depend()
      return @state
 
    setState: (value) ->
      return if value is @state
      @state = value
      @_deps['state'].changed()
 
    # none reactive getters and setters
    getAmountInt: () ->
      return @amountInt
    setAmountInt: (value) ->
      @amountInt = value
    getVat: () ->
      return Math.round(@amountInt * 0.19)
    getTotal: () ->
      return @getAmountInt() + @getVat()
 
    getName: () ->
      return @name
    setName: (value) ->
      @name = value
 
    getAddress: (value) ->
      return @address
    setAddress: (value) ->
      @address = value


The getState() and setState() functions are reactive because we call depend() on every get. That tells Meteor that to keep track of everyone who needs to know the value of this function. If we set the variable we call changed() and this tells Meteor to reactively execute everything that depends on this Deps.Dependency. And this is exactly the same we would achieve with our Session variable. Also make sure to not call changed() if there was no change, because rerunning all depending functions can be expensive.

We instantiate the template helper class CreditPurchase on template.created:

# make the creditPurchase instance locally available
creditPurchase = null
 
##############################
##### CREDIT PURCHASE TEMPLATE
##############################
Template.creditPurchase.created = () ->
  # create our helper class when the main template is created
  # do not do this on rendered, because it could be called multiple
  # times
  creditPurchase = new CreditPurchase()
 
 
Template.creditPurchase.helpers({
  stateIsOrder: () ->
    # getState() is reactive
    return creditPurchase.getState() is CreditPurchase.ORDER_STATE
})


In the first lines we simply created an empty variable which is locally available in this file. We use this variable to access our template helper class, for example in the stateIsOrder helper of the creditPurchase templateThis helper returns true if the state is 0 and false if not. Because this is reactive, we only need to change the state value of the template helper class and the correct template is rendered.

##############################
##### ORDER FORM TEMPLATE
##############################
Template.orderForm.events({
  'submit form': (evt, tpl) ->
    evt.preventDefault()
 
    creditPurchase.setAmountInt(parseInt($('#amountInt').val()) * 100)
    creditPurchase.setName($('#name').val())
    creditPurchase.setAddress($('#address').val())
 
    # because setState() is reactive the orderConfirmation
    # template will be rendered automatically
    creditPurchase.setState(1)
})
 
Template.orderForm.helpers({
  amount: () -> return formatAmountInt(creditPurchase.getAmountInt())
  name: () -> return creditPurchase.getName()
  address: () -> return creditPurchase.getAddress()
})


The state of our template helper class is changed to 1 if the form is submitted and so the orderConfirmation template is rendered automatically. Also there are some helpers that return values depending on the current state of our template helper class. No need for any Session variables and also no need for reactivity where none is needed. It is also nice, that you can define the representation of the values depending on the template for example by adding some formatting.

At last we want to return to the edit state if we click on the edit button. And this is as easy as this:

##############################
##### ORDER CONFIRMATION TEMPLATE
##############################
Template.orderConfirmation.events({
  'click .edit': (evt, tpl) ->
    evt.preventDefault()
    creditPurchase.setState(0)
    return false
})
 
 
Template.orderConfirmation.helpers({
  amount: () -> return formatAmountInt(creditPurchase.getAmountInt())
  vat: () -> return formatAmountInt(creditPurchase.getVat())
  total: () -> return formatAmountInt(creditPurchase.getTotal())
  address: () -> return creditPurchase.getAddress()
})


We only need to change the reactive variable state of our template helper class to 0.

And that's it. If you liked this little example, feel free to follow me on twitter.

If you want to learn more about Deps.Dependency, this are good ressources:
Why Is My Meteor App Not Running Reactively? by Robert Dickert

EventedMind Screencasts
Introduction and Build A Reactive Data Source

©️ 2024 Digitale Kumpel GmbH. All rights reserved.