Using Computed Properties To Lazily Load Data
Following up on yesterday’s computed properties post, here is a useful technique for lazily loading data using computed properties. Let’s say your contacts application will show you a list of all the email you have exchanged with a particular contact. This is a lot of detail that you generally will not want to load from the server but you need to have it available anyway.
How would we construct such a thing?
Well, a simple way to start would be to create a collection view that uses a property (called “messages”) on your Contact as its content:
<%= list_view :bind => { :content => "Addressbook.currentContact.messages" }, :content_value_key => :subject %>
This view helper would show a list view with the messages array. But, this means that your Contact record has to include an array of all the messages. We just said we didn’t want to load those messages immediately, but only when the user views a contact’s details. How do we do this without having to write a lot of glue code?
Turns out computed properties come to the rescue. Here is some sample code we could write:
Addressbook.Contact = SC.Record.extend({
// ... other useful code
messages: function() {
if (this._messages) return this._messages; // use cache if present
// otherwise, load from server
Addressbook.server.loadMessagesForContact(this);
return [];
}.property(),
// this method is called by Addressbook.server.loadMessagesForContact() when
// the messages have loaded. The passed value is an array of
// Addressbook.Message records.
messagesDidLoad: function(messages) {
this.propertyWillChange('messages') ;
this._messages = messages; // save in cache
this.propertyDidChange('messages') ;
}
});
This code assumes that you also have an object called Addressbook.server with a method loadMessagesForContact() which will, as the name says, load the messages for the named contact and then calls the messagesDidLoad() method on the contact with the array of messages.
There are two really important things to notice about this code:
First, the computed property simply returns a cached value or, if it does not have the cached value, requests the value from the server. When you first select a contact, your list view will initially display an empty list while it kicks off the request for more data in the back end.
Second, the messagesDidLoad() method, which is called when the values have loaded from the server, simply saves these in the cache and then tells SproutCore that the value of the messages property has changed. If you are still viewing this contact’s details when this happens, this change notification will trigger the binding to your ListView and cause it to request the value of messages again, now with the loaded data.
Later, if you view other contacts and come back to this one, the messages will still be cached and will display immediately without refreshing the server. Simple, lazily loaded data and, best of all, no glue code.
What’s This propertyWillChange/properyDidChange Thing?
The two methods called in messagesDidLoad() here might be a new trick for you. What do these methods do? In fact, the code you see in messagesDidLoad() is very similar to what happens whenever you call set(). They simply tell SproutCore that the value for the named property has likely changed. This will in turn cause SproutCore to notify any observers and trigger bindings.
Usually you do not need to call these two methods yourself, but, as you can see here, they have their place. You can use this technique anytime you have a “virtual” property like this one. Note that the messages property above does not explicitly list any dependent property keys. This is because the property depends on the calls to propertyWillChange()/propertyDidChange() in messagesDidLoad() to tell SproutCore when the property has changed instead.
And What If My Data Changes?
Finally, let me show you a common variant on this technique that can make it even more useful. The above technique is useful if the data you are loading lazily will never change once you have loaded it once. But what if this is not the case? Maybe a new message has been received since the last time you viewed a contact. How can you make sure your data always stays up to date?
It turns out this is quite easy. Just rewrite your messages computed property like so:
messages: function() {
// always refresh
Addressbook.server.loadMessagesForContact(this);
// then return the current value, if there is one
return this._messages || [] ;
}.property()
Now every time some other object asks for the value of messages, it will check the server for any changes. Of course you could rate limit this if you want to implement any other policy to control how often you check for new messages, but the principle is the same.
Computed properties are a powerful way to lazily compute - and load - data in your application. When used with bindings, they make it easy for you to keep the data you load at one time to a minimum. Hopefully this little demonstration will makes it a little easier to do that.

Discussion Area - Leave a Comment