• Meteor

Build A Reactive Data Source

From the class:  Tracker

We've looked at the computation object, but we haven't spent a lot of time discussing why a computation object is required at all. So in this lesson we're going to build up a simple reactive data source. And that should show you why the computation object is used.

So instead of just printing out to the console "hello world," let's now actually grab the value of a reactive data source that we're going to build up together. And by building up this reactive data source together, you should get some better intuition about what's happening under the hood with Tracker. I'm going to call this reactive data source Name because what it's going to do is just return the name of some person.

It's going to be an object-- I'm using object literal syntax here-- and what I want to be able to do is from say hello, from this function, I want to be able to grab the name by calling the get function on name. So this is very similar to how you would use session, right? You call session.get, and you grab a key. But in this case, there's only one key. It's going to be the name. So this is going to be something like a reactive variable.

So let's define a get function. And what I want the get function to do is to return the value, the current value, of the name. And we'll just give it an initial value of Chris. And then in say hello, we'll print out what the current name is. And in the console, we should see the name printed.

Now what I want to be able to do is to also have a set function. So I'll define a function called set, and it will take a value. And it will set the current value to the new value. And we can be a little smart about it. We could say if the value is not equal to what we already have, then go ahead and change it.

What I need this reactive data source to do, in order to make it reactive, I want it to rerun the say hello function any time the value changes. And we can use computations to accomplish this. The trick here is that I'm calling the get function. So inside this function, I have the opportunity to perform some logic. So I can check to see whether or not we're currently inside of a computation.

So at this point in the call stack, do we have an active computation? And if we do, then let's go ahead and grab it. So I'm just going to abbreviate this with the variable c. And we'll grab it with the current computation variable. And then I'm going to store it in an object. I'll create the object up here. I'm just going to call it comps for short, and it'll be an object where we're going to store a map of computations or IDs to computations. So I'll say this.comps, and we'll store the ID and set that equal to the computation.

All right. Now the other side of this equation is that when I set the value, when I change it to something different, I want to loop through these computations and call the invalidate function on them. So I can say for each of the IDs in the computations objects that we stored-- oops-- for each of the ID, go ahead and grab it. And then just call the invalidate function.

Before we run this code, let's walk through it. It starts down here where we create the computation at this point in the stack by calling the auto run function and passing this function value in as a parameter. Then inside the function, we call the get function of this reactive data source. And in doing that, it gives the reactive data source a chance to see whether or not we're in a computation, whether one's been set. And if so, to go ahead and store it on this object here.

So to store it away so that at a later time we can invalidate it. And then just return the value. Now, next time we go and we call this set function, and if we change the value, we can loop through all these stored computations and invalidate each one of them. And that will cause this function to rerun, just like we've seen already.

So let's go over and look at that in the console to make sure that it's working. And to test it, let's see it run for the first time. And then I'm going to say name set, and we'll just set it to Evented Mind. And sure enough, my function say hello returns or reruns, and we see the name Evented Mind again. And so let's just do that a few more times to make sure that there's no bugs.

Great. So each time I call set, it triggers an invalidation, and the function is rerun. So things are working as expected. Now let's get a little bit more insight into what's happening by putting a debugger statement into the get function itself. So right inside of the get function here, I'll just put a debugger statement. And that'll allow us to take a look at what the stack is at the time that this function gets called.

We'll make it a little bit larger here so we can see what's happening. And take a look at the stack over on the right. Here's where we're calling auto run, and here's where the computation gets created. Let me pick a nicer color so we can see. Here's the auto run, and here's the computation. So that's getting created here at a previous point in the call stack. Then we call our function, say hello.

And inside the say hello function, we call the get function of the reactive data source. And that's where we are right at this point. Now, when we check to see whether or not tracker is active-- that means that a computation was set at some previous point-- the answer will be yes, it is. Because we've set a computation. So let's step through the debugger and see that that is true.

It is, and so now we get the current computation. And by looking into the local scope variables down here, we can take a look at the object. Notice it has an ID, just like before. And we're going to take that and put this object, this entire object, onto this map. We're going to store it away for later. We're going to store it by its ID. And that's what this line right here is doing. And then just return the value that we have currently. OK

Now let's take a look at that name reactive data source in the console. I'll line a watch expression, and I'll just type the name variable. And that'll let us inspect the object up here close to the call stack. And notice it currently has a value of Chris, and this comps object is storing the current computation that we were running previously.

Now let's go and change the value of the reactive variable, but we'll put a debug statement in the set function first. I'll call this set function with a value of two, and I went ahead off camera and put the debug statement in there. And here we get dropped into the set function, and we'll step through it. First we'll set the new value.

And let's open up the name object in our watch so that we can see, now the new value is two. And now we're going to loop through all of these computations, and just call invalidate on each one. Now again, this queues up the invalidation, or it queues up the computation to rerun at a later time. So we're not going to see the names printed quite yet. But then when I press Play, you'll see the function rerun again. So there's name two printed to the console.

Now one of the great things about this design, or one of the interesting things about it, is that this name reactive variable doesn't need to know anything about this function. So notice we're not calling say hello directly by name in here. All we're doing is checking to see whether or not there's a current computation. So this level of indirection allows code that was written by different people to both participate in the reactive system. The only requirement is that a computation be created at some point previously in the call stack.

So to recap what we just looked at here, we created a reactive variable called Name. And in that we're going to store an object of computations by ID. So this'll be a computation C1. And we could store as many as we want because keep in mind that the reactive variable could be used in multiple places. And then every single time we call a set, just loop through. Let me change the color. We can just loop through these computations and just invalidate each one of them.