[Solved] Client-side observer hanging around after template destroyed

i have a Blaze template in which i’ve set some listeners on a cursor:

Template.FooTemplate.onCreated(function() {
	let self = this;

	self.subscribe('foo', function() {
		let cursor = Foo.find();

		cursor.observe({
			added: function(newDocument, oldDocument) {
				console.log('added');
			},
			changed: function(newDocument, oldDocument) {
				console.log('changed');
			}
		});
	});
});

after the template is destroyed, i continue to get the changed() handler called when i do operations on Foo. i have confirmed the template is destroyed by logging onDestroyed(). and the listener doesn’t stick around for just a short time, it’s definitely hanging around.

any idea why, and what’s a good way to allow the listener to release when the template is destroyed?

From the docs:

observe returns a live query handle, which is an object with a stop method. Call stop with no arguments to stop calling the callback functions and tear down the query. The query will run forever until you call this. If observe is called from a Tracker.autorun computation, it is automatically stopped when the computation is rerun or stopped…

thanks, that matches what i’m seeing. i can certainly call stop() on that in the onDestroyed(). i’m just surprised that it doesn’t fall out of scope and unsubscribe itself.

the docs mention Tracker.autorun() blocks, any idea how this.autorun() blocks created in onCreated() are handled?

I would have thought the same, but I’ve never actually tried that.

solution: i saved off the handle from the cursor’s observer() and called stop() on it in onDestroyed().

@robfallows, thank you.

1 Like

I’m wondering if it wouldn’t be better to wrap your subscribe and observe in a self.autorun block? I’ve found cleanup on a this.autorun to be more consistent than relying on the onDestroyed function.

it’s interesting about that consistency. i haven’t done enough (in general) with onDestroyed() to notice an inconsistency.

yeah, i’m not sure what’s better. maybe it works better in an autorun(), but it feels a little funny to me because i’m not intending for that block to be reactive in a normal autorun() kind of way. i.e. the code might be misleading, whereas the intent of the onDestroyed() is clear.

can you say more about the inconsistency you’ve seen?

I think that is a valid assessment.

In my use cases, I found that depending on what I was doing with routing, onDestroyed didn’t always fire when I expected it to, which meant that sometimes pointers/references were left dangling and not getting cleaned up the way that they were supposed to. Although, the blame could be entirely placed on my implementation.

I guess my struggle with onDestroyed has less to do with consistency (as I’m not doubting the efficacy of the function) and more to do with timing (as is often the case in Meteor). I guess I just spend less time when I leverage the auto-cleanup of template instances and things like comp.stop().