Over the last few years, Javascript has grown up a bit. Vue.js is a relatively new library for building scalable, interactive experiences for the web. For developers, the way of making interfaces reactive and dynamic was long-winded and, at times, painful. It often felt like there was an unavoidable separation between what you want to react (html) and what was used to make it react (js).

While the concept of Separation of Concerns is important, this felt a little more like being detached, but tightly coupled. You found yourself “digging into the DOM” in order to pull values and fumbling through chaining actions together instead of truly “describing” how something should work. Let’s scratch the surface…

Data-driven vs. DOM-driven

What does that even mean? Here’s what DOM-driven looks like this.

In html:

<div class="message"></div>

In js:

functiongetDynamicText() {     return'This is the text'; } $('.message').text(getDynamicText()); 

There is absolutely nothing wrong with this code. It’s gonna work. Ultimately, we can inject some text into an element with the class message. The only issue is that the js needs to know exactly what that selector’s class name is in order to target and populate it with some text.

Take the example of coming back to a project six months later and needing to make some styling adjustments -I might remove the class “message” from the element all-together, not realizing it had “functionality” associated with it. Now the text is no longer added to our element since the DOM doesn’t contain an element with .message on it.

In the past, we’ve remedied this a bit by prefixing element classes with .js in order to clearly see in the html that some bit of functionality is associated and/or manipulating the element but very quickly you don’t know what is happening to it until you head over to the js. This was a way to try to separate form (css) vs. function (js) selectors. Totally works, but it can be better.

But, data…

So what does data-driven look like? Well, at first glance, it’s about the same amount of code.

In html:

<div class="app"> 
     <div class="message">{{ msg }}</div>
</div>

In js:

new Vue({ 
       el: '.app', 
       data: { 
             msg: 'This is the text' 
       } 
})

You might look at this and say, “Well, Vue is using .app in order to target, so how is that any better?” I totally get it. but this really just defines the scope of Vue. In most cases this isn’t going to ever change. However, I can go and change .message to .whatever and our string in msg is still going to output. Things don’t break and visually, looking at the html, I can tell exactly where my msg text will render.

“Well, so what? It’s a different way to do the same thing.” True. But, let’s look at a more involved scenario, one where there’s some interactivity – the classic todo list.

The jQuery Way

This is not intended to come across as a bash on jQuery. I like jQuery. It’s gotten me through a lot of tough times. There are other ways though…

For a to-do list, the objective would be to add text into an input field, click on a button, and have the input’s value populate a list below it. If you’ve used jQuery before you’ve probably done something like this…

In html:

<div class="app"> 
    <div class="message"></div> 
    <input class="input"> 
    <button class="add">Add</button> 
 
    <ul class="todos"></ul> 
</div>

In js:

var error   = 'Please enter some text',         success = 'Todo added';      $('.add').on('click', function(){     var val = $('.input').val();     if(val != ''){$('.todo').append('<li>'+val+'</li>');         $('.message').removeClass('error').text(success);         $('.input').text('');} else {$('.message').addClass('error').text(error);}});

I’ve simplified this as much as I can between the markup and js and added a conditional to handle our validation messaging. But isn’t it kinda gross to have to concatenate the list item with the value coming from the input field? $(‘.todo’).append(‘<li>’+val+‘</li>’); Again, if my classes change, I’m (kinda) screwed. You can run into additional issues if other plugins are injecting their own markup into the DOM, then you really don’t know for certain what’s going on.

The Vue.js Way

Vue’s js doesn’t really care about the DOM, it cares about the data. Don’t get me wrong – Vue cares, but ultimately, it’s data that drives most of its functionality.

Looky:

 <div class="app"> 
       <div class="message" :class="{'error' : msg == error}"> 
             {{ msg }} 
       </div> 
       <input v-model="newTodo"> 
       <button @click="add">Add</button>
 
       <ul class="todos"> 
            <li v-for="todo in todos">{{ todo }}</li> 
       </ul>
</div>

new Vue({ 
       el: '.app', 
       data: { 
       msg: '', 
       todos: [], 
       newTodo: '', 
       error: 'Please add some text', 
       success: 'Todo added' 
    },
    methods: { 
          add: function() { 
               if(this.newTodo != ''){ 
                     this.todos.push(this.newTodo)); 
                     this.msg = this.success; 
                     this.newTodo = ''; 
               } else {
                     this.msg = this.error; 
               }
          } 
     } 
});

Yep, it’s a little more code but the differences in how things are happening are immediately clear. Let’s start with the markup. Just looking at it, I can tell what it is going to do and I’ve decided what elements should be part of the scaffolding without having to parse through any js yet.

Vue adds some syntactic sugar to the DOM though. Look at how I can conditionally add classes to the element in the reactive :class attribute. It’s pretty much saying if the condition is true, add this class, if not, don’t worry about it (more on dynamic :attributes at another time).

Check out the @click attribute on the button. This says, “when clicked, call the add method.” The javascript doesn’t care what triggered it unlike the first example where it had to be the selector with the class of .add. In fact, in the Vue example, there is no reference to the DOM at all. We’re simply setting our data to values and the html is reacting to the changes.

Notice the to-do list. The list items are in the markup as opposed to concatenation in the javascript. We use v-for to loop through our todos data in order to output the list. Since todos starts out empty, there would be nothing in the list. If we set the data in the js to have some values already, the list would “automatically” render the items.

... 
todos: [ 
      'Pick up beer.', 
      'Research drones', 
      'Cancel subscription' 
] 
...

Without doing anything else we’d get:

  • Pick up beer
  • Research drones
  • Cancel subscription

Doing the same thing with jQuery would get sorta ugly, quick:

var error = 'Please enter some text', 
    success = 'Todo added', 
    todos = [ 
      'Pick up beer.', 
      'Research drones', 
      'Cancel subscription' 
    ];

$.forEach(todos, function(item){ 
      $('.todo').append('<li>'+item+'</li>'); 
}); 

$('.add').on('click', function(){ 
      var val = $('.input').val(); 
      if(val != '') { 
            $('.todo').append('<li>'+val+'</li>'); 
            $('.message').removeClass('error').text(success); 
            $('.input').text(''); 
      } else { 
            $('.message').addClass('error').text(error); 
      } 
});

There’s so much DOM in there. What if, for whatever reason we decided we didn’t want to use a li for a todo and we changed to div or something. At this point, with the jQuery version, we’d be changing our ul to div in the markup then updating the li in multiple places in the js. Sure, we can refactor the jQuery version to make the rendering a function to keep it consistent but we’d still have to update our selectors in multiple places. It’s just kinda “meh.”

(This only helps some)

function renderListItem(val) { 
      $('.todo').append('<li>'+val+'</li>'); 
}

My view logic is tightly coupled to my functionality but I’m editing them in different places. This is not good.

Conclusion

Alright, I know, that’s only a tiny taste of Vue 2.0. Trust me, there’s more. There’s a lot more. Filters, computed values, single file components to name a few. There also other things to consider and understand, like, how is SEO impacted by Vue’s dynamic content? And how does this impact the browser since it’s doing so much work now? Good questions. But that’s for another time.

So, yeah, seriously, get in there, tinker a bit. More later.

Let's talk

"*" indicates required fields

This field is for validation purposes and should be left unchanged.