Metaprogramming and client side storage with EmberJS

Chris Atkins
The Ember Way
Published in
6 min readNov 16, 2014

--

Building an Ember friendly LocalStorage adapter

When building web applications we often need to persist little bits of data or configuration that isn’t strictly part of our application’s model layer. When working on Ruby on Rails apps, I tend to reach for the ultra snazzy data structure server Redis for that kind of thing, and so far it’s worked a charm. When working on ambitious front end applications, the kind that EmberJS excels at, we also have a need to store transient data, and we have a few different options.

I’ll start off with a bit of background on browser based storage, and move in to a more concrete example of how we can make Local Storage integrate with EmberJS.

TLDR; — Check out a working example on JSBin.

Cookies

A well supported option is to use cookies. Cookies have a fairly straightforward API, but have a couple of downsides. All data stored in cookies gets sent to your sever with every request, bloating your bandwidth use. Also, we’re limited to about 4KB of storage with cookies.

Web Storage

Another option is to use the Web Storage API. The HTML5 Web Storage API provides us simple key value storage up to about 5MB, which should be plenty for a our kind of workloads. There are two main flavours of web storage, namely Session Storage and Local Storage, each with the same API.

Session Storage lets us persist data for the duration of a session.

A page session lasts for as long as the browser is open and survives over page reloads and restores. Opening a page in a new tab or window will cause a new session to be initiated. — Mozilla DOM Storage Guide

Local Storage

Local Storage on the other hand lasts indefinitely, between browser restarts and computer reboots, and will stick around until the user clears their browsing data. Let’s take a look to see how it works. Feel free to pop open your Javascript console (Command + Option + J on Chrome for OSX) and try out the examples below.

https://gist.github.com/catkins/447294d1003fd4423427

As we can see, the localStorage object can be used much in the same way as a normal JavaScript object, with a couple of extra methods on board. What if we want to store something a little bit more complex, like an an array, or object?

https://gist.github.com/catkins/ddd3f79fb25493941778

As we can see, localStorage calls toString() on each value as we save it, so we’ve hit a small hurdle. Luckily, the browser’s built in JSON methods jump in and save the day.

https://gist.github.com/catkins/d507d2196fb929b4e2e7

Now we can save and load anything that JSON supports and all is good in the world.

Walking the Ember Way

EmberJS gets a lot of it’s strength by augmenting JavaScript’s Object Model with some of it’s own abstractions including bindings and computed properties. With a little bit of legwork, we can build a lightweight wrapper for the localStorage object that lets us embrace the Ember Way of doing things, and also makes it a lot easier to mock out in tests.

First lets take a look at the completed storage wrapper, and I’ll walk through each of it’s parts.

https://gist.github.com/catkins/51a2c1cc1549e068e344

Note: this example is using the Globals method of bootstrapping an Ember app, but would only take small modifications to work with ES6 modules.

Our wrapper is created by simply extending from Ember.Object and implementing the properties we’d like. This gives us the base we need to let us bind to properties in localStorage.

https://gist.github.com/catkins/eb8c4d95a22a21340a1e

In this case, we’re defaulting the persistence to use window.localStorage object, but you could as easily switch out the persistence for window.sessionStorage, or any other object that implements the same public API. This is particularly useful for unit testing, where you’d likely want to stub out the actual storage using SinonJS or another mocking framework.

We’re also providing a namespace for our keys. On the server, I tend to use the Redis Namespace gem to keep keys used by my app seperate from ones used by caches etc. whilst using the same Redis database and connection. We can take a similar approach on the client side. Some javascript libraries, particularly analytics scripts, make use of localStorage to get their work done. We don’t want to step on each other’s toes so we’ll be prefixing all of our keys with the namespace provided here.

https://gist.github.com/catkins/25514b854248dca3b02f

With our storage service being able to store arbitrary information, we’re not going to know all of the properties that can be stored ahead of time. We could implement properties for each key we want to store, but that would get unwieldy very quickly, so we turn to meta-programming for help.

When we call Ember.Object#get with a property name that hasn’t explicitly been implemented, the call is delegated to the unknownProperty method. When implemented, we can create virtual properties on our object. Ruby developers would be familiar with this sort of behaviour with the method_missing method.

In our case, our virtual property delegates the method to the localStorage object. We generate the key with the namespace, fetch the data, deserialize it as JSON, and voilà, we have our data. Having the method implemented as a computed property, we can now bind to the values in templates, and observe them for changes.

How about setting keys you ask? Well, we do the same thing in reverse.

https://gist.github.com/catkins/fb5a13475c786bbe27cd

We namespace the key, serialize the value as JSON and save the JSON string onto localStorage. To make sure that our bindings stay up to date, we have to notify anything binding to the virtual property by calling the notifyPropertyChanged method.

When calling Ember.Object#set with a key that isn’t defined ahead of time, it first calls the setUnknownProperty method. If that method is implemented and returns undefined, then the setter will set the property directly on the object. If however, the method returns a non-undefined value, it will do nothing afterwards as the implementation has flagged it as handled.

Communication between tabs

For bonus points, we’ll also implement a handler for the storage event which is fired when storage is updated in other tabs.

https://gist.github.com/catkins/3145bee11b76f4e4e907

When our storage service is created, we set up an event handler to monitor the storage event. We basically check to see if the key modified is in our app’s namespace, and if it is, notify all observers that the property is updated. Now we can have properties in sync between open tabs with minimal fuss.

Wiring it all up

Now that we’ve implemented the storage service, we need to get it into our application and do something useful with it. For this, we need to make use of an initializer to inject the service into our objects. Read more in the docs for Ember.Application#initalizer.

https://gist.github.com/catkins/1c2805b91cf443180402

Here we’ve registered our service into the applications container and made it available as the storage property in all of our routes and controllers. And we’re all set up. We can bind to this property in templates, controllers and routes and it will stick around, and stay in sync.

https://gist.github.com/catkins/2de77bc6758f85a97862

Have a look at this complete example on JSBin. Open a few tabs, restart your browser,

Thanks for reading!

This post ended up being quite long, but if you’ve made it this far, thanks for reading, I hope that this helps you understand a little bit more about meta-programming, and how easy EmberJS makes to get things done.

If you’ve got any feedback, or I’ve said something silly, feel free to hit me up on twitter at @itscatkins.

Also quick shoutout to my co-workers at Doceo @sugarpirate_ and @ctountzis who have been churning out some great articles on EmberJS, and inspired me to start writing again.

--

--