Skip to content

Instantly share code, notes, and snippets.

@themeteorchef
Created September 8, 2015 13:05
Show Gist options
  • Save themeteorchef/367feabf6035258fb297 to your computer and use it in GitHub Desktop.
Save themeteorchef/367feabf6035258fb297 to your computer and use it in GitHub Desktop.
Common Meteor Patterns in ES2015

As we prepare for support of ES2015 in Meteor, it's important that we understand what supporting ES2015 will look like and how it will impact our applications. In this snippet, we'll take a quick look at common Meteor patterns rewritten using ES2015. While this won't be a deep dive into ES2015 itself, it should be enough to help you understand how to bring existing and new applications up to date with the latest version of JavaScript.

Example Code

In order to illustrate usage of ES2016, we'll be using some of the code from the Building Complex Forms recipe.

Template Logic

When we say "Template Logic" in Meteor, we're referring to the JavaScript that's paired with our HTML/Spacebars templates. First, let's look at an example of some template logic written in ES5. This is taken from the template logic paired with the order form in the Building Complex Forms recipe.

/client/templates/public/order.js

Template.order.onCreated( function() {
  this.subscribe( 'order' );

  this.currentOrder = new ReactiveDict();

  if ( Meteor.user() ) {
    this.currentOrder.set( "type", "My Pizzas" );
  } else {
    this.currentOrder.set( "type", "Popular Pizzas" );
  }

  this.currentOrder.set( "pizza", { "name": "Pick a pizza!", "price": 0 } );
});

Template.order.onRendered( function() {

  var template = Template.instance();

  $( "#place-order" ).validate({
    rules: {
      customPizzaName: {
        required: true
      },
      name: {
        required: true
      },
      telephone: {
        required: true
      },
      streetAddress: {
        required: true
      },
      city: {
        required: true
      },
      state: {
        required: true
      },
      zipCode: {
        required: true
      },
      emailAddress: {
        required: true,
        email: true
      },
      password: {
        required: true,
        minlength: 6
      }
    },
    submitHandler: function() {
      var orderData = template.currentOrder;
          type      = orderData.get( "type" ),
          pizza     = orderData.get( "pizza" ),
          order     = {};

      if ( Meteor.user() ) {
        order.customer = Meteor.userId();
      } else {
        order.customer = {
          name: template.find( "[name='name']" ).value,
          telephone: template.find( "[name='telephone']" ).value,
          streetAddress: template.find( "[name='streetAddress']" ).value,
          secondaryAddress: template.find( "[name='secondaryAddress']" ).value,
          city: template.find( "[name='city']" ).value,
          state: template.find( "[name='state']" ).value,
          zipCode: template.find( "[name='zipCode']" ).value
        }

        order.credentials = {
          email: template.find( "[name='emailAddress']").value,
          password: template.find( "[name='password']").value
        }
      }

      if ( type === "Custom Pizza" ) {
        var meatToppings    = [],
            nonMeatToppings = [];

        $( "[name='meatTopping']:checked" ).each( function( index, element ) {
          meatToppings.push( element.value );
        });

        $( "[name='nonMeatTopping']:checked" ).each( function( index, element ) {
          nonMeatToppings.push( element.value );
        });

        var customPizza = {
          name: template.find( "[name='customPizzaName']" ).value,
          size: template.find( "[name='size'] option:selected" ).value,
          crust: template.find( "[name='crust'] option:selected" ).value,
          sauce: template.find( "[name='sauce'] option:selected" ).value,
          toppings: {
            meats: meatToppings,
            nonMeats: nonMeatToppings
          },
          custom: true,
          price: 1000
        };
      }

      if ( pizza.name === "Pick a pizza!" ) {
        Bert.alert( "Make sure to pick a pizza!", "warning" );
      } else {
        order.pizza = pizza._id ? pizza._id : customPizza;

        Meteor.call( "placeOrder", order, function( error, response ) {
          if ( error ) {
            Bert.alert( error.reason, "danger" );
          } else {
            Bert.alert( "Order submitted!", "success" );

            if ( order.credentials ) {
              Meteor.loginWithPassword( order.credentials.email, order.credentials.password );
            }

            Router.go( "/profile" );
          }
        });
      }
    }
  });
});

Template.order.helpers({
  customer: function() {
    if ( Meteor.userId() ) {
      var getCustomer = Customers.findOne( { "userId": Meteor.userId() } );
    } else {
      var getCustomer = {};
    }

    if ( getCustomer ) {
      getCustomer.context = "order";
      return getCustomer;
    }
  },
  order: function() {
    var currentOrder = Template.instance().currentOrder,
        type         = currentOrder.get( "type" ),
        pizza        = currentOrder.get( "pizza" ),
        price        = currentOrder.get( "price");

    if ( type !== "Custom Pizza" ) {
      var getPizza = pizza._id ? Pizza.findOne( { "_id": pizza._id } ) : pizza;
    } else {
      var getPizza = {
        name: "Build your custom pizza up above!",
        price: 1000
      }
    }

    if ( getPizza ) {
      return {
        type: pizza.name !== "Pick a pizza!" ? type : null,
        pizza: getPizza,
        price: getPizza.price
      }
    }
  }
});

Template.order.events({
  'click .nav-tabs li': function( event, template ) {
    var orderType = $( event.target ).closest( "li" ).data( "pizza-type" );

    template.currentOrder.set( "type", orderType );

    if ( orderType !== "Custom Pizza" ) {
      template.currentOrder.set( "pizza", { "name": "Pick a pizza!", "price": 0 } );
    } else {
      template.currentOrder.set( "pizza", { "name": "Build your custom pizza up above!", "price": 0 } );
    }
  },
  'click .pizza': function( event, template ) {
    template.currentOrder.set( "pizza", this );

    if ( this.custom ) {
      template.currentOrder.set( "type", "My Pizzas" );
    } else {
      template.currentOrder.set( "type", "Popular Pizzas" );
    }
  },
  'submit form': function( event ) {
    event.preventDefault();
  }
});

That's a pretty big chunk of code! Let's refactor this using ES2015 to see how we can simplify it a bit. We'll step through each template method : onCreated, onRendered, helpers, and events.

Template.order.onCreated in ES2015

Template.order.onCreated( () => {
  this.subscribe( 'order' );

  this.currentOrder = new ReactiveDict();

  if ( Meteor.user() ) {
    this.currentOrder.set( "type", "My Pizzas" );
  } else {
    this.currentOrder.set( "type", "Popular Pizzas" );
  }

  this.currentOrder.set( "pizza", { "name": "Pick a pizza!", "price": 0 } );
});

Yep, that's it! Pretty simple, right? Here, the only thing we've changed is the function declaration to use the Arrow syntax. Everything else stays the same.

Template.order.onRendered in ES2015

Template.order.onRendered( () => {

  let template = Template.instance();

  $( "#place-order" ).validate({
    rules: {
      customPizzaName: {
        required: true
      },
      name: {
        required: true
      },
      telephone: {
        required: true
      },
      streetAddress: {
        required: true
      },
      city: {
        required: true
      },
      state: {
        required: true
      },
      zipCode: {
        required: true
      },
      emailAddress: {
        required: true,
        email: true
      },
      password: {
        required: true,
        minlength: 6
      }
    },
    submitHandler() {
      let orderData = template.currentOrder;
          type      = orderData.get( "type" ),
          pizza     = orderData.get( "pizza" ),
          order     = {};

      if ( Meteor.user() ) {
        order.customer = Meteor.userId();
      } else {
        order.customer = {
          name: template.find( "[name='name']" ).value,
          telephone: template.find( "[name='telephone']" ).value,
          streetAddress: template.find( "[name='streetAddress']" ).value,
          secondaryAddress: template.find( "[name='secondaryAddress']" ).value,
          city: template.find( "[name='city']" ).value,
          state: template.find( "[name='state']" ).value,
          zipCode: template.find( "[name='zipCode']" ).value
        }

        order.credentials = {
          email: template.find( "[name='emailAddress']").value,
          password: template.find( "[name='password']").value
        }
      }

      if ( type === "Custom Pizza" ) {
        let meatToppings    = [],
            nonMeatToppings = [];

        $( "[name='meatTopping']:checked" ).each( ( index, element ) => {
          meatToppings.push( element.value );
        });

        $( "[name='nonMeatTopping']:checked" ).each( ( index, element ) => {
          nonMeatToppings.push( element.value );
        });

        let customPizza = {
          name: template.find( "[name='customPizzaName']" ).value,
          size: template.find( "[name='size'] option:selected" ).value,
          crust: template.find( "[name='crust'] option:selected" ).value,
          sauce: template.find( "[name='sauce'] option:selected" ).value,
          toppings: {
            meats: meatToppings,
            nonMeats: nonMeatToppings
          },
          custom: true,
          price: 1000
        };
      }

      if ( pizza.name === "Pick a pizza!" ) {
        Bert.alert( "Make sure to pick a pizza!", "warning" );
      } else {
        order.pizza = pizza._id ? pizza._id : customPizza;
		
        function placeOrder( order ) {
          return new Promise( ( resolve, reject ) => {
            Meteor.call( "placeOrder", order, ( error, response ) => {
              if ( error ) {
                reject( error.reason );
              } else {
                resolve( "Order submitted!" );
              }
            })
          })
        }
        
        placeOrder( order ).then( message => {
          Bert.alert( message, "success" );
          
          if ( order.credentials ) {
            Meteor.loginWithPassword( order.credentials.email, order.credentials.password );
          }
          
          Router.go( "/profile" );
        }).catch( error => {
          Bert.alert( error, "danger" );
        })
      }
    }
  });
});

Still pretty straightforward, but this one requires a bit of explanation in some parts. First, notice that we've swapped all usage of var with let. let is the counterpart to const in ES2015. With let, we create a variable that can be reassigned later. In contrast, with const, we create a constant variable, meaning it cannot be changed later. Because we're creating a number of variables that will change at some point, it's safe to use let here.

Notice, too, that we've gone through and replaced all instances of function(){} with the new arrow syntax like () => {}. These are mostly harmless and really just help to save a few keystrokes (we'll see some situations later on where these may not be appropriate). The functions we're calling here are pretty beefy, so they all rely on the Statement body syntax of Arrow functions.

Another thing to point out is how we've defined our submitHandler function. Notice that instead of defining it like submitHandler: function(){}, we've shortened it down to submitHandler() {}. What gives? This is part of the new enhanced object literals feature in ES2015. Inside of object literals, now, we can define methods using the functionName() {} syntax. Again, just another "keystroke saver," but handy nonetheless.

The real behemoth in this is the refactor of our method call to placeOrder on the server to use Promises. While not entirely necessary here—it's just cool to show off how it works—we can see a few neat things going on that are good to know for later. Let's break that piece out on its own and then step through it.

Promise Wrapped Methods in ES2015

function placeOrder( order ) {
  return new Promise( ( resolve, reject ) => {
    Meteor.call( "placeOrder", order, ( error, response ) => {
      if ( error ) {
      	reject( error.reason );
      } else {
      	resolve( "Order submitted!" );
      }
    });
  });
}

placeOrder( order ).then( message => {
  Bert.alert( message, "success" );

  if ( order.credentials ) {
  	Meteor.loginWithPassword( order.credentials.email, order.credentials.password );
  }

  Router.go( "/profile" );
}).catch( error => {
  Bert.alert( error, "danger" );
});

So what the heck is happening here? The purpose of a Promise is to allow us to call asynchronous funtions in a synchronous manner. This means that instead of having to nest a bunch of if/else statements, we can have a simple syntax for saying "do this, and if that works, do this, and then if anything fails, do this." The point being that we get a much cleaner syntax for defining calls to asynchronous functions that are expected to succeed or fail at some point in the future.

In order to define a new Promise, we need to create a new function that accepts any arguments we'd like to pass to the function we're wrapping in a Promise. Let that sink in. In the case of our example, we're trying to pass an object order to our placeOrder method. So, we make sure to pass order as an argument to our new placeOrder function that will wrap our Promise. Hang in there!

Next, we define our Promise new Promise returning it from our placeOrder function and passing two arguments resolve and reject. These arguments each represent an "event" of sorts in relation to our promise. When our function works as expected, we resolve it, when it fails, we reject it. Inside of our Promise definition, we can see a standard Meteor.call( "placeOrder" ), taking the order argument we passed (containing our object with order information).

Finally, we pass a regular callback function checking for an error argument. But wait! Notice that instead of just calling the code we might inside of our if/else block, we instead return either reject()—invoking it as a function and passing our error message—or, we return resolve(), also invoking it as a function but passing a success message.

This is all gibberish until we move down a few lines to where we actually call our placeOrder function. At this point, we've created a new promise. To determine what happens next, we rely on chaining a .then() method. Pay attention here. We handle what happens when our function is called successfully first—that resolve() part—and at the end, we chain on a .catch() method to handle any errors that occur.

As many then()s as you'd like

We've only showcased the usage of one then() method here, but in practice, we can chain multiple then()s if we'd like. This allows us to avoid nesting a bunch of callbacks while passing along the result from each successive function call. The only thing to note is that your catch() method will always come last.

In our example, if our Promise resolves as expected, we pass the success messsage "Order submitted!" from within in our Promise to our call to Bert.alert(). The string we passed in the resolve, then, is equivalent to the message argument we pass to our .then() method. Notice that the same thing applies with our .catch() method on the end. That error argument being passed to Bert.alert() maps to the error.reason we passed to the reject() method inside of our Promise. Wow!

While we have added a bit of code here, what we pick up in return is a much cleaner—and arguably, predictable—syntax. We can see the actual steps that our function goes through, both in good and bad scenarios. Neat!

That was a hell of a detour! Let's jump back up to refactor the helpers for our order template into some spiffy ES2015 code. Don't worry, that Promise thing was as hardcore as we get.

Template.order.helpers in ES2015

Template.order.helpers({
  customer() {
    if ( Meteor.userId() ) {
      let getCustomer = Customers.findOne( { "userId": Meteor.userId() } );
    } else {
      let getCustomer = {};
    }

    if ( getCustomer ) {
      getCustomer.context = "order";
      return getCustomer;
    }
  },
  order() {
    let currentOrder = Template.instance().currentOrder,
        type         = currentOrder.get( "type" ),
        pizza        = currentOrder.get( "pizza" ),
        price        = currentOrder.get( "price");

    if ( type !== "Custom Pizza" ) {
      let getPizza = pizza._id ? Pizza.findOne( { "_id": pizza._id } ) : pizza;
    } else {
      let getPizza = {
        name: "Build your custom pizza up above!",
        price: 1000
      }
    }

    if ( getPizza ) {
      return {
        type: pizza.name !== "Pick a pizza!" ? type : null,
        pizza: getPizza,
        price: getPizza.price
      }
    }
  }
});

Easy peasy. Two changes to point out here. We've swapped all of our vars with lets and then we've updated each of our helper function definitions to use the new enhanced object literal syntax of defining methods. So, instead of customer: function(){}, we get customer() {}. Underwhelming for everyone but our keyboards.

Last up, event maps! Let's take a peek.

Template.order.events in ES2015

Template.order.events({
  'click .nav-tabs li': ( event, template ) => {
    let orderType = $( event.target ).closest( "li" ).data( "pizza-type" );

    template.currentOrder.set( "type", orderType );

    if ( orderType !== "Custom Pizza" ) {
      template.currentOrder.set( "pizza", { "name": "Pick a pizza!", "price": 0 } );
    } else {
      template.currentOrder.set( "pizza", { "name": "Build your custom pizza up above!", "price": 0 } );
    }
  },
  'click .pizza': function( event, template ) {
    template.currentOrder.set( "pizza", this );

    if ( this.custom ) {
      template.currentOrder.set( "type", "My Pizzas" );
    } else {
      template.currentOrder.set( "type", "Popular Pizzas" );
    }
  },
  'submit form': ( event ) => {
    event.preventDefault();
  }
});

Just two changes here: swapping functions to use the Arrow syntax and changing any vars to lets. Wait a second! Notice that our second event is not using the Arrow syntax. Why is that? Well, when we use the Arrow syntax, this is equal to a lexical this. What that means is that this is not equal to the current function, but rather, to the parent context, or Window. Deep breaths. This is pretty confusing. This Stack Overflow answer helped me to understand it a bit better:

Lexical Scoping defines how variable names are resolved in nested functions: inner functions contain the scope of parent functions even if the parent function has returned.

— via What is Lexical Scope on Stack Overflow

Making some sense? So, with a plain function(){}, this is equal to function this is being referenced in. With Arrow syntax, this is equal to the outer parent functions.

Don't panic, but we're about to see something similar with Publications.

Curious?

It's far too long to post here, but if you're curious, you can console.log( this );, alternating between the regular function() {} style and the Arrow syntax () => {}. The first version will return this as expected (the current data context of the template this event is being called on), while the latter will return the Window object in all its glory.

Publications

At this point, you should notice a recurring theme: the bulk of the code we'll write in Meteor using ES2015 is not scary. A lot of the changes we're making are minor and mostly cosmetic. As another example, let's look at setting up a pubication.

Publications in ES2015

Meteor.publish( 'pizzaProfile', function() {
  let user = this.userId;

  let data = [
    Pizza.find( { $or: [ { "custom": true, "ownerId": user }, { "custom": false } ] } ),
    Customers.find( { "userId": user } ),
    Orders.find( { "userId": user } )
  ];

  if ( data ) {
    return data;
  }

  return this.ready();
});

Just when you thought it was all blue skies! Nothing to panic about here, but notice that we're not using the Arrow syntax to define our function here. Again, this is because of the lexical scope of Arrow functions. Because we're referencing this.userId, we need access to the server object that we normally have access to in publications. To do it, we have to call a plain function() {}. Otherwise, this will be equal to the entirety of Meteor (the main object that holds all of the properties, methods, etc for Meteor). Yikes!

Okay. That's the last heart attack. From here on out everything is pretty easy going.

Routes

Routes are pretty basic. For good measure:

Defining Routes in ES2015 (and Iron Router)

Router.route( 'profile', {
  path: '/profile',
  template: 'pizzaProfile',
  onBeforeAction() {
    Session.set( 'currentRoute', 'profile' );
    this.next();
  }
});

It won't get much crazier than that. For clarity sake, if this route were defined in Flow Router, we may see something like this:

Defining Routes in ES2015 (and Flow Router)

FlowRouter.route( '/profile', {
  action() {
    BlazeLayout.render( 'applicationLayout', { main: 'pizzaProfile' } ); 
  },
  name: 'pizzaProfile'
});

Again, the only thing really changing here is that we're swapping method declarations from method: function() {} to just method() {}.

Speaking of methods, what do Meteor Methods look like in ES2015?

Methods

Another example taken from Building Complex Forms, this one is the counterpart method being called by the method call we refactored to use Promises earlier.

Meteor.methods({
  placeOrder( order ) {
    check( order, Object );

    const handleOrder = {
      createUser( credentials ) {
        try {
          let userId = Accounts.createUser( credentials );
          return userId;
        } catch( exception ) {
          return exception;
        }
      },
      createCustomer( customer, userId ) {
        customer.userId = userId;
        let customerId  = Customers.insert( customer );

        return customerId;
      },
      createPizza( pizza, userId ) {
        pizza.ownerId = userId;

        let pizzaId = Pizza.insert( pizza );
        return pizzaId;
      },
      createOrder( userId, pizzaId ) {
        let orderId = Orders.insert({
          userId: userId,
          pizzaId: pizzaId,
          date: ( new Date() )
        });
        return orderId;
      }
    }

    try {
      let userId     = order.credentials   ? handleOrder.createUser( order.credentials )          : order.customer,
          customerId = order.customer.name ? handleOrder.createCustomer( order.customer, userId ) : null,
          pizzaId    = order.pizza.custom  ? handleOrder.createPizza( order.pizza, userId )       : order.pizza;
          orderId    = handleOrder.createOrder( userId, pizzaId );

      return orderId;
    } catch( exception ) {
      return exception;
    }
  }
});

Straightforward? Because our method is being defined inside of an object literal, we can use the new method() {} syntax to kickoff our method definition. Inside, we make use of const to define an object handleOrder which is then assigned a series of methods (we use the enhanced object literal syntax again to define each). The thing to call out here is our usage of const. Because this object contains a series of methods that we do not want to change, we use const to block any accidental reassignment of the handleOrder object later.

To make that clear, if beneath the handleOrder definition we called some code like handleOrder = "bye bye methods", we'd get an error. If we used let to define handleOrder, then, it would be reassigned to "bye bye methods" and we'd see our castle come tumbling down. This is one of the scenarios you will want to watch out for when trying to figure out whether to use let or const.

That's it for Methods! Not much to change aside from the code you've defined in them.

Loops

To better handle loops, we get a new type of loop for( var example of examples ) {}. You may be thinking to yourself..."we already have this?" Close! Right now we have access to for( var example in examples ) {}. Spot the difference? One is using in and the other is using of. What's the difference?

When using the in version of the for loop, var example is equal to the current property name. In the of version of the for loop, var example is equal to the current property value. Let's look at another example from our Building Complex Forms recipe.

Using for In loops in ES5

createPizzas = function() {

  var pizzas = [
    {
      "name": "Classic Supreme",
      "crust": "Thin",
      "toppings": {
        "meats": [ 'Sausage', 'Pepperoni' ],
        "nonMeats": [ 'Green Peppers', 'Mushrooms', 'Black Olives', 'Onions' ]
      },
      "sauce": "Tomato",
      "size": 14,
      "price": 1000,
      "custom": false
    },
    {
      "name": "Chicago",
      "crust": "Deep Dish",
      "toppings": {
        "meats": [ 'Pepperoni' ],
        "nonMeats": [ 'Banana Peppers', 'Green Peppers', 'Mushrooms', 'Black Olives', 'Onions' ]
      },
      "sauce": "Robust Tomato",
      "size": 12,
      "price": 1500,
      "custom": false
    },
    {
      "name": "Classic Pepperoni",
      "crust": "Regular",
      "toppings": {
        "meats": [ 'Pepperoni' ],
        "nonMeats": []
      },
      "sauce": "Tomato",
      "size": 12,
      "price": 1000,
      "custom": false
    }
  ];

  var pizzaCount = Pizza.find().count();

  if ( pizzaCount < 1 ) {
    for ( var pizza in pizzas ) {
      Pizza.insert( pizzas[ pizza ] );
    }
  }
};

And now, let's refactor this to use ES2015 and the of syntax for our for loop:

Using for of loops in ES5

createPizzas = () => {

  let pizzas = [
    [...]
  ];

  let pizzaCount = Pizza.find().count();

  if ( pizzaCount < 1 ) {
    for ( let pizza of pizzas ) {
      Pizza.insert( pizza );
    }
  }
};

The difference here is subtle. Can you spot it? Before, when we were looping our pizzas and inserting one into the Pizza collection, we had to call Pizza.insert( pizzas[ pizza ] );. This translated to us selecting the current pizza being looped over in the pizzas array. With the new of syntax, we can instead just grab the pizza we want to insert directly and call Pizza.insert( pizza );. Remember, this works because of tells our loop to assign the current property value being looped, not the property name.

Isn't there other stuff?

Quite a bit! Because our focus was just on rewriting common Meteor patterns in ES2015, we've left out a few features. Before we part ways, though, there are two features we should call out as they're pretty handy for Meteor apps: classes and modules.

ES2015 Classes

While not necessary to define basic Meteor code, a feature of ES2015 that we can start using to organize our own code is classes. Classes are just a syntactic wrapper around prototyping in JavaScript. They're designed to give a bit of structure to the process of defining methods on and creating instances of objects. Here's a quick example:

Example Using Classes

class Taco {
  constructor( name, toppings ) {
    this.name     = name;
    this.toppings = toppings.slice( 0, toppings.length - 1 ).join( ", " ) + `, and ${ toppings.pop() }`;
  }

  makeTaco() {
    console.log( `Nothing like a ${this.name} taco with ${this.toppings}!` );
  }
}

fiestaTaco = new Taco( "Fiesta", [ 'Beef', 'Cheese', 'Lettuce', 'Tomatoes', 'Gravy' ] );

While we probably won't be making a lot of tacos in our Meteor applications—especially with gravy as a topping—this example shows how a class can work to give our code some structure. Notice, a class behaves just like a regular prototype object. Classes are great for code that we'll need to call again and again but with different properties.

In this example, we define our Class and then add a method called constructor() at the top (a convention introduced in the Class specification). Inside, we assign some values for the current instance of our Class using this. We assign two properties to our Class instance: name and toppings. Name is pretty straightforward: it just gets assigned to whatever we pass in the first argument when creating an instance of our class new Taco( "This Argument" );.

Next, we get a little tricky and assign our Class instance's this.toppings value equal to a string we build using the toppings argument that's passed along when creating an instance of our class new Taco( "Name", [ 'These', 'Are', 'What', 'We', 'Want' ];. To build that string, we use another feature of ES2015, Template Strings, to allow us to do something called String Interpolation. In normal people words, interpolate means to evaluate the code passed within ${} and print its value in the string at that point (where ${someExpression} acts as a placeholder).

In our example, then, after we've split off the last topping in our array, we append it using a Template String. Notice that unlike a normal string where we'd use quotes "", to make use of Template Strings we have to define our string using backtics ``. How about them apples.

Once our Constructor() is defined, we add a method to our class called makeTaco() that simply logs a string to the console. Again, we make use of Template Strings to get this working. Lastly, we assign an instance of our Taco class to a global variable called fiestaTaco. If we open up our browser console then—assuming this code is defined on the client—we'd get something like this:

Browser Console

fiestaTaco.makeTaco();
> "Nothing like a Fiesta taco with Beef, Cheese, Lettuce, Tomatoes, and Gravy!"

We can create as many instances of Taco as we want now, passing any name or toppings that we wish. Pretty neat.

ES2015 Modules

Another feature that's not quite supported in Meteor is the usage of Modules. Modules allow us to break our code up into individual files (e.g. defining a class in its own file) and then import those files—or code—into other files. Because Meteor's current design automatically imports all of our files for us, this functionality will not work. There are plans, however, to enable this in a future version of Meteor. Fingers crossed!

This snippet will update

As features start to emerge for supporting different features of ES2015 in Meteor (and as I understand more and more about it), this snippet will be updated. Stay tuned!

Takeways

  • ES2015 is just the new version of JavaScript. It only introduces syntax changes and new features, not a radical new version of the language.
  • When using the Arrow syntax with functions, be careful with lexical scope returning the parent scope instead of what you intended.
  • Start using ES2015! Full support will come in Meteor 1.2 but we can start playing with it now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment