In this post we'll look at the collection of technologies that Meteor provides to enable 'Optimistic UI' updating. This capability is crucial to user expectations for modern web apps and is fundamental to Meteor.
But there's one part that represents the core of the Meteor platform: full-stack data integration with DDP, LiveQuery, and Minimongo. In this case the full-stack integration provides real value that would be hard to rebuild from separate components. Let me tell you why.
Your app should be able to respond to user inputs faster than it takes to make a whole roundtrip to the server — we call this Optimistic UI updating. It's very hard to build an app that correctly implements client-side simulations as it takes a lot of work to make your UI consistent, avoid loading duplicate data over and over, and keep your client up to date with your server data. Keep reading to see why you need this, why it's hard and how Meteor makes it easy.
Users of modern web apps expect the UI to respond instantly
When a user pushes a button in a website or a mobile app, they don't want to wait for a request to be sent all the way to the server to calculate the new state of the screen. Using the basic AJAX model of calculating the results on the server and then displaying them to the user will cause your app to feel laggy, and sometimes inconsistent with the user's input. Mobile developers especially need to worry about this because cellular networks can be unreliable, sometimes taking a second or more to deliver a result from your server.
There are four elements required to satisfy this expectation and requirement. Let's explore those.
1. To have your app respond instantly, you need to render the UI on the client
This is one explanation for the rise of client-side rendering frameworks like Angular and React — you need to be able to compute your HTML directly in the browser to be able to update the view without doing a round trip to the server. Whenever the user takes an action, you need to first update your UI to make things look fast, then send a request to the server to do that modification on the real database. At Meteor, we call this Optimistic UI, or latency compensation.
In order to make optimistic updates and be able to predict the state of the UI after some user action, you will need to keep some of your application data on the client.
One option is to directly store things inside the front-end framework's local state — Angular's
$scope or React's component state — but you can imagine how this would easily result in duplication of data or inconsistency. If you have two widgets on the page showing the same data in different ways, then they will each have one copy of the data, and worse those copies might not be consistent. Having two UI elements on your page showing inconsistent results sounds like a great way to end up with confused users.
So what's the solution - how do we keep all of our UI state de-duplicated and internally consistent?
2. To have your UI consistent and avoid data duplication, you need a global data cache on the client
If you want your UI to be consistent and avoid loading the same data sets over and over again, you need to render everything from the same data source. When you make some action on the page and update this global data source, that can trigger updates in all of the relevant UI elements on the page to keep everything consistent. It's almost like magic — you can update your whole UI before you even hit the server.
Minimongo is Meteor's single source of truth for the client
Run the same queries on the client and server
3. To populate your data cache, you need a protocol for data subscriptions
Now that you know you need to put some of our data in a local cache on the client, you need a way to get it there and keep it up to date. This is where another core part of Meteor comes in — data subscriptions over DDP, Meteor's data synchronization protocol.
When a new part of your application is displayed, it can request data to be loaded by registering a subscription, which is a request to the server to send over some data and put it into Minimongo on the client. Now, you can just render your UI components from Minimongo, and trust that the correct data will be there as soon as it is loaded. When the data is no longer needed, you can just unsubscribe and it will go away.
Subscriptions in Meteor have some awesome features:
- They de-duplicate data. You will never end up with two copies of the same data inside Minimongo.
- They deliver realtime updates. You don't have to manually poll or ask for updates; when new data shows up on the server, it will be automatically delivered to all of the clients that have subscribed to it.
Subscriptions and Minimongo also work together with Meteor methods, which are the last piece of the puzzle to enabling correct optimistic UI rendering.
4. To solve client and server disagreements, you need to be able to patch your UI with the real result
When you are doing optimistic UI updates, the client tries to predict the outcome of some operation. Most of the time, this works great, but sometimes the client just doesn't have enough information to make an accurate prediction. Maybe it's missing some permissions data, maybe it doesn't know about some modification that a different client made, etc. Plus, as any experienced developer will know, you can't ever trust client code to do the right thing since users can modify the code running in their browser.
At first, this sounds like an easy problem — of course, the server always wins, so can't you just use the result from the server? The answer is yes, but you need to make sure you have rolled back all of the operations done on the client as part of the optimistic UI update. It turns out it is actually really hard to do this when you have multiple user actions happening in parallel.
To run an optimistic update and then roll back the changes to make room for the server result, you need the last piece of the Meteor data story: Methods.
Meteor methods are functions that run first on the client against Minimongo, then on the server, on the real MongoDB. Any modifications made during the simulation on the client are tracked, and then rolled back when the real modifications from the server are reported. You don't need to do anything about tracking changes on the client and rolling them back - you get automatic eventual consistency with the server, and it just works.
Meteor provides these benefits no matter which front-end framework you are using
As you may have noticed throughout this article, none of these features depend on your rendering framework. You can use Meteor's integrated rendering engine Blaze, the Meteor-Angular project, or one of several React integrations, and still get all of the benefits of Meteor's data caching and optimistic UI updates. If you are writing a native iOS app, you can also use the excellent iOS-DDP project, which implements the same front-end logic but switches out Minimongo for iOS CoreData, letting you write your app in a completely native style.
Try it out for yourself
If you're not convinced yet that Meteor will let you build a first-class user experience without any of the hassle, try this experiment:
First, complete the official Meteor tutorial. Then, try building the same app with all of the same features, including optimistic UI updates and data caching, without Meteor. Measure how long it takes and how many lines of code you had to write — I bet you'll find it will take a lot less time and code with Meteor. Let me know how it goes!