Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Meteor's advanced responsiveness


May 10, 2021 Meteor


Table of contents


Advanced responsiveness

Although it is rare to need to write your own code to track dependent variables, it is necessary to understand the workflow for dependent variables.

Imagine that we now need to track the number of Facebook posts on Microscope that current users' Facebook friends are "like". L et's assume that we've solved the problem of Facebook user authentication, used the correct API calls, and parsed the data. We now have an asynchronous client function that returns the number getFacebookLikeCount(user, url, callback)

It is important to remember that this function is very non-responsive and non-real-time. I t initiates an HTTP request to Facebook, gets some data, and then returns it to our app as a callback function parameter. But if the like number changes and the function doesn't run again, we won't be able to get the latest data on our interface.

To solve this problem, we first setInterval to call this function every few seconds:

currentLikeCount = 0;
Meteor.setInterval(function() {
  var postId;
  if (Meteor.user() && postId = Session.get('currentPostId')) {
    getFacebookLikeCount(Meteor.user(), Posts.find(postId).url,
    function(err, count) {
      if (!err) {
        currentLikeCount = count;
      }
    });
  }
}, 5 * 1000);

Any time we check currentLikeCount variable, we expect to get accurate data within 5 seconds. W e are now using this variable in the help method. The code is as follows:

Template.postItem.likeCount = function() {
  return currentLikeCount;
}

However, we can't currentLikeCount time the currentLikeCount changes. Although the variable itself can now be pseudo-real-time, it is not responsive so it cannot communicate correctly with the rest of the Meteor ecosystem.

Tracking Reactivity: Computations

Meteor's responsiveness is controlled by dependency, which is a data structure that tracks Computation.

As we've seen in the Responsive section before, a computation is a piece of code used to process responsive data. I n our example, there is a computation implicitly built postItem as a template. Each help method in this template has its own computation.

As you can imagine, this computation is a piece of code that focuses exclusively on responsive data. When the data changes, the computation notifies invalidate() and it is the computation that determines what needs to be done.

Turn variables into responsive functions

Putting the currentLikeCount into a responsive data source, we need to track all the computations that depend on the variable. This needs to change it from a variable to a function (a function with a return value):

var _currentLikeCount = 0;
var _currentLikeCountListeners = new Tracker.Dependency();

currentLikeCount = function() {
  _currentLikeCountListeners.depend();
  return _currentLikeCount;
}

Meteor.setInterval(function() {
  var postId;
  if (Meteor.user() && postId = Session.get('currentPostId')) {
    getFacebookLikeCount(Meteor.user(), Posts.find(postId),
    function(err, count) {
      if (!err && count !== _currentLikeCount) {
        _currentLikeCount = count;
        _currentLikeCountListeners.changed();
      }
    });
  }
}, 5 * 1000);

We built a _currentLikeCountListeners which tracks all computations that use currentLikeCount() When _currentLikeCount changes, we notify all computations data of the change by calling the dependent changed() function.

These computations can continue to process the following data changes.

You might think it's like a lot of references on a responsive data source, and you're right, Meteor provides a lot of tools to make the job simple (you don't need to call computations directly, they run automatically). T here's a package reactive-var and that's exactly what currentLikeCount() does. Let's join this package:

meteor add reactive-var

Use it to simplify our code a bit:

var currentLikeCount = new ReactiveVar();

Meteor.setInterval(function() {
  var postId;
  if (Meteor.user() && postId = Session.get('currentPostId')) {
    getFacebookLikeCount(Meteor.user(), Posts.find(postId),
    function(err, count) {
      if (!err) {
        currentLikeCount.set(count);
      }
    });
  }
}, 5 * 1000);

Now with this package, we call currentLikeCount.get() and it will work as before. There is another useful reactive-dict which provides key-value storage Session

Comparing Tracker to Angular

Angular is a client-responsive library developed by Google guys. L et's compare how Meteor and Angular rely on tracking. They are implemented in very different ways.

We already know that Meteor uses some code called comptations to implement dependency tracking. T hese computations are tracked by special "responsive" data sources (functions) that mark themselves as invalidate as the data changes. W hen the invalidate() to be called, the responsive data source, displayed, notifies all dependencies. Note that this is the general case when data changes, and data sources can trigger invalidation for other reasons.

In addition, although computations usually simply re-run when the data is invalidate, you can specify any behavior you want at this point. These give users high responsive control.

In Angular, responsiveness is scope by the scope object. A scope can be seen as a normal js object with some special methods.

When your responsive data depends on a value in scope, scope.$watch to tell the expression what data you care about (for example, what data you care about in the scope) and a listener that runs every time the expression changes. So you need to show what you have to do when the expression data changes.

Going back to the previous Example of Facebook, our code can be written as follows:

$rootScope.$watch('currentLikeCount', function(likeCount) {
  console.log('Current like count is ' + likeCount);
});

Of course, just as you rarely need to set up computations in Meteor, in Angular you don't have to show that calling $watch ng-model {{expressions}} and then they handle re-showing when the data changes.

When the responsive data changes, scope.$apply() is called. He recalculates all watchers in scope, and then calls only the listener method of the watcher where the expression value changes.

So scope.$apply() very similar to the dependency.changed() that it operates at the scope level, rather than giving you control to decide which listener needs to be re-evaluate. In other words, less control allows Angular to decide which listeners need to be re-evaluated in a smart and efficient way.

In Angular, our getFacebookLikeCount() like this:

Meteor.setInterval(function() {
  getFacebookLikeCount(Meteor.user(), Posts.find(postId),
  function(err, count) {
    if (!err) {
      $rootScope.currentLikeCount = count;
      $rootScope.$apply();
    }
  });
}, 5 * 1000);

It must be admitted that Meteor has done most of the heavy lifting of responsive work for us, but hopefully, learning from these patterns will help you with your in-depth research.