Using a compute and can.batch() to listen to top level state changes


#1

Hi All,

I had a performance issue in a CanJS 2.3 app while importing a list of about 200 variables. I assumed it was rendering issue, but when can.batch.() did not fix the problem, Justin helped me find the actual issue and fix the code. Lesson I learned here - don’t assume (duh), and always use dev tools to analyze performance to find the cause. More importantly, can.compute with batching let me do something once instead of 2000 times with basic event listeners.

The original app problem and it’s slow solution:
CanJS changes to the list of variables on a parent guide property needed to also update a global, window.gGuide.vars, for legacy code in the app to be aware of the CanJS updates. The original solution was using:

appState.bind('change', (event, attr) => {
    test if the event was for `guide.vars` changing, and if so update `window.gGuide.vars`
}

This high level app-state binding on any ‘change’ event firing was generating 1000s of events, each attempting to redraw the entire page, causing the performance issue. Bad news and falls in the ‘should never do’ camp.

The solution still requires the use of can.batch.start() & stop() during the large update to the guide.vars Map, but uses a compute at the top level appState which is smart enough to wait for the batch to complete, and only listens for changes on guide and guide.vars, drastically reducing the number of events:

var vars = can.compute(function () {
    var vars = appState.attr("guide.vars");
    if (vars) {
      return vars.serialize();
    }
});
vars.bind("change", function (ev, newVars){
    if (newVars) {
      window.gGuide.vars = newVars;
    }
});

Hope this helps someone else!
Mike

P.S. Justin also riffed an idea for a CanJS 4.x solution using connectedCallback and listento:

.extend({
    tag: "cali-app",
    ViewModel: {
        get serializedVars(){
            return this.guide?.vars.serialize()
        },
        connectedCallback(el){
            this.listenTo("serializedVars", function(ev, vars){
                can.queues.traceStack()
                window.gVars = vars;
            })
        }
    }
})