AngularJS with IndexedDB using a helper library
In working on my podcast app, podrad.io, I wanted to be able to persist data, so that users could keep their subscriptions between visits, without losing anything, and without my needing to do anything server-side. When I was writing podrad.io, however, my goal was to get something written as quickly as I could, get it out, and then iterate on it as I have time. With those goals in mind, I used LocalStorage for persistence, it was simple, and did the job. Now, I’m going back and looking at what needs to be added or fixed, and the data persistence is top on my list.
HTML5 client-side data persistence
While I’m sure most of you know this already, there are three competing APIs for doing data persistence client-side in HTML5, LocalStorage, IndexedDB and WebSQL. LocalStorage is supported everywhere, but it only allows the mapping of strings, which means that you are serializing and de-serializing data whenever you want to touch the disk. That’s fine for really small amounts of data, but it won’t scale, unless you want to design around this odd constraint. WebSQL and IndexedDB both have support in various browsers, however neither of them are supported everywhere, and WebSQL is being deprecated in favor of IndexedDB. WebSQL is a limited SQL implementation that uses sqlite3 (again, it’s not part of the HTML5 spec, so it’s a non-starter). IndexedDB is an indexed NoSQL object store, meaning that you create a data-store and put your objects there. As I mentioned, IndexedDB isn’t fully supported yet, however there is a polyfill lib that helps here.
If you look at some code that uses IndexedDB, one of your first reactions will likely be, ‘gee, that’s an unnecessarily verbose API!’ That was my first reaction too. My second reaction was that the code linked above doesn’t work, fantastic. As I was googling around for a fix, I stumbled upon IDBWrapper, a wrapper class for IndexedDB. The API for the wrapper is quite simple, and easy to understand; to me, it’s what the IndexedDB API should have looked like - or at least closer to that.
I wrote a sample todo list app (in AngularJS) that uses some of IDBWrapper’s functionality.
Generic bits, index.html and app.js
Starting with the index file:
I cut out all of the redundant, and auto-generated bits, and just left this with the bare-bones for us to look at. There are a couple of interesting pieces here. First, I used Font Awesome for the icon font, I’ve found that to be easier to deal with that Bootstrap’s font, and it scales better. Next, you’ll see that I included the IndexedDBShim, and according to the documentation there, that’s all that I should need to do to use it. However, I’m not sure if IDBWrapper will use it or not, I need to look into that. Finally, I installed IDBWrapper with Bower, so it’s in my components directory.
App routing in app.js:
No surprises here, just your standard app.js file.
Main view and controller
We’ve got two basic parts the input and the display for our todo list. The input is a basic form that populates
$scope.itemname in our controller, and calls
$scope.addItem() when submitted. The view part simply iterates over the items array and displays the contents of each object, using
ng-repeat. We also have a delete button that calls
$scope.deleteItem(id) on the list item that we want to remove. Pretty standard stuff.
The controller handles those calls for
deleteItem, as well as does our querying of the data store. Very simply, we start out by initializing the data store, then getting any existing objects within it, and update the view with those. We need to call
$scope.$apply() in the
getItemsSuccess callback because otherwise, our view does not get updated. Here’s a write-up on why that is. As you can see, the IDBWrapper API is quite straight-forward, and easy to use.
That was all pretty simple stuff, I liked the wrapper, and will probably use it in the future. One thing that I’m not sure on is whether or not it will use the shim if it’s available, and I’ll be looking into that shortly. Full code available on GitHub.