This is a direct continuation from my Angular Lesson 4. I ended that post feeling no small amount of bafflement at the behavior of MobX in an Angular environment using mobx-angular. We were also studying the idea of an Angular structural directive in general.
Adam Klein, the author of mobx-angular did respond to my direct question about his code. (I wasn’t kidding when I suggested that. If the person is alive, you usually get best results by just asking the author.) But I didn’t ask him for a lengthy explanation and hand-holding. Just for a pointer in the right direction. And I need to process what he said and make sure I’ve understood correctly.
Let’s recall what the mystery was. Inside Klein’s MobxAutorun directive, we see this code, except I removed some extraneous stuff that does not affect our discussion.
autorun( () => view['detectChanges']() );
Here view is a reference to the Angular embedded view. My question was simply: why do we see different behavior when running
view['detectChanges']() inside this autorun function versus running it outside—you know, just anyplace where view is in scope. The difference I saw was that when its inside this autorun function, for some reason view.detectChanges does not cause an unnecessary recompute of the computed values. But outside of autorun, it does.
When I asked Klein for help understanding this, he responded to me. The actual text of Klein’s response was this: “It doesn’t prevent Angular’s CD from running, that’s why it makes sense to use it with OnPush. It will cache computed values though because it observes them with autorun.”
I had to think that over a bit before it made any sense to me. I explained it further to myself and asked Klein to confirm if I had it. I wrote, “Thanks. I appreciate that you responded at all. Maybe I understand. Executing inside autorun is what causes mobx to cache the values. Then on rendering Angular gets the cached values–doesn’t recompute. Without autorun, no values are cached.”
And then me again: “Please correct me if I’m wrong. This is still just a guess. Mobx only computes and caches those computed values if a function like autorun is watching them. Being displayed in the template is not enough to notify mobx to compute and cache the values. Do I have it?”
Klein responded, “Exactly.”
I don’t want to get bogged down with explaining what I thought was happening before, because I had it wrong. I’ll just explain what is happening and how I verified it.
Like I said to Klein, even though I had certain variables declared as computed using the @computed decorator, MobX was not notified to compute and cache those values. That is the notion of lazy computation at work (which is a good thing). MobX will only actually compute and cache those @computed values if it sees them being used someplace. And referencing those values in the component’s template it not one of the things MobX watches for in this regard. In other words, just putting these computed values in the view template alone was not enough to notify MobX that it actually needed to compute and cache them.
To clarify that we have understood these matters now, I added a little something to the source code for this lesson. Recall, it’s on my angular-examples repo (
https://github.com/xerocross/angular-examples ) on branch mobx-computed-values. In the example as written there now, I added a debug function for view.detectChanges called view.detectChangesDebug. My new debug function logs “detect changes” to the console and then executes view.detectChanges. Also now we put this new debug function inside the autorun instead of detectChanges. This works the same as using detectChanges there except now we have console logs.
I also made it so if you just click on the html element containing the view (anywhere) that also executes view.detectChangesDebug. You can see that it logs to the console, and you can also see that it does not re-compute the computed values (because those functions log to the console, and those logs don’t appear).
To summarize all that: so long as we run the view.detectChanges inside an autorun once, the relevant computed values get computed and cached. Then subsequent view updates do not recompute them unnecessarily, even if we explicitly fire those view updates using hacky and contrived experimental means. But they will be recomputed if they need to be recomputed based on the values that go into them changing. And all that is as expected. Mystery solved.
Mr. Michel Weststrate (@mweststrate), author of MobX, jumped on this Twitter exchange I had with Adam Klein and added the following: “note that caching can be enabled to be done even when the value is not observed. It is just not the default because it is easy to create memory leaks this way.” That’s cool. definitely something to bear in mind for later use cases.
I like MobX. It’s big, but I think I could see myself using it later for other things and learning more about how to take full advantage of it.