Preventing Observable Updates to Parent Component

Let’s say I have parent component A with child component B like this:

<component-A>
   <component-B/>
</component-A>

Now, component-B has some observable properties which are used to rerender component-B. How do I stop it from also rerunning computes on component-A that reference properties on B?

If you are wondering why methods on A have access to B, it is to inject data from A processes into B when needed.

Can you describe the question with a jsbin?

I’m not sure what you mean by “How do I stop it from also rerunning computes on component-A that reference properties on B?”

What computes? Why are they being rerun?

Hi Justin, thanks for responding.

It will take me a bit of time to set up a jsbin. In the meantime, what is happening is in app.js I import my logger module to log messages.

    currentPresentation: {
    	get() {
            var logger = services.getService('logger');
            logger.debug('AppVM getting Sign VM');
    		var cp = SignVM().attr('currentPresentation');
    		logger.debug('Get Current Presentation: '+cp.id);
    		return cp;
    	}
    }

if I change a property on the logger, such as toggling the view for the logger,

	showLog: function () {
		this.attr('showlog', true);
		this.log('Showing Log');
	},
	hideLog: function () {
		this.attr('showlog', false);
		this.log('Hiding Log');
	},
	toggleView: function () {
		this.attr('showlog') ? this.hideLog() : this.showLog();
	},

which updates the logger stache template to hide the logger (display:none), then it reruns the above getter for currentPresentation.

It sounds to me like the currentPresentation getter rerunning is a bug where the setters in it are being observed. See #2220 which should be fixed in the next release.

This can happen if you are running a map constructor or calling attr() with an object as the first argument.

ok, guess I’ll wait for the next release. For now it’s only a minor nuisance. thanks for the response.

When will the fix with #2220 be released?

I heard it should be out Monday or Tuesday.

You could also try adding the fix to your local CanJS code (i.e., node_modules) to confirm whether this solves your problem. It’s just changing an instance of each() to _each().

ok, where in the code do I make the change?
nevermind, didn’t notice the link to the fix.

nope, didn’t fix it.

Hmm, can you confirm that showlog isn’t being read by one of those calls in the currentPresentation getter? If it’s not, then it seems like a bug.

For example, if logger.debug() checks to see if the logger is visible before logging, that would totally cause this. Same with services.getService('logger') or SignVM().attr('currentPresentation'). Any reads are going to cause dependencies because the getter is basically a compute.

I now see the issue in one specific instance. When I clear the logger, I simply set the observable messages Array to new List(). Since this destroys the old list and replaces with a new one, it seem to call observable update.

So what is the best way to clear a can.List without actually replacing the list with a new instance?

while(messages.length)
  messages.pop();

?

I take it back. While showLog() seems to work fine, if I toggle the actual logging it also recomputes currentPresentation.

yes, debug checks to see if logging is enabled before logging the message. So how can I stop the compute from propagating up to currentPresentation.get()?

You could use logger.attr('messages').replace([]). It will not fire a “messages” event.

One way would be to change the get() in currentPresentation to value() so that it is only run during instantiation, so there will be no recomputes. This may not be suitable if you are trying to read other virtual properties in the value() method, or if you want it to recompute sometimes.

There are other ways to block observation such as using ___get() on the map or wrapping your call in can.__notObserve(), but since those are “private”, I’m not sure if there is an official way. Sometimes direct property reads like map.foo could work for concrete properties (doesn’t work for get()) but I heard Justin wants that to go away in 3.0.

Usually this kind of problem can be avoided/fixed by just rethinking what you are doing.

well, I do want to recompute when new data is retrieved from the server. I just don’t want it to happen on logger changes.

Also, I am not entirely clear on when a compute is recalculated. I thought anytime set is called. The next call to get should recompute. (this is really a new question) but I am seeing when I set the logging level to a new value with a set(). when it calls attr on the prop, it does not recompute.

	currentLevel: {
		type: 'number',
		get: function () { 
			return (!isNaN(this.attr('storage').get('loglevel')) ? 
					this.attr('storage').get('loglevel') : this.attr('levels').DEBUG);  // defaults to DEBUG
		},
		set: function (newLevel) {
			this.log('Logging Level set to: '+this.attr('levels').convert(newLevel));
			this.attr('storage').store('loglevel', newLevel);
			return newLevel;
		}
	},

There are quite a few rules about this so I’ll provide an example:

fullName: {
  get() {
    return this.attr('firstName') + ' ' + this.attr('lastName');
  }
}

If fullName is bound, then it will be observed for changes by observing its dependencies. For instance, if you bind to fullName, it implicitly binds to firstName and lastName. When either changes, fullName recomputes and then whatever observes fullName also recomputes but ONLY if the output of fullName’s get() changed.

If you set a property that has a get() defined, the set value is passed to get(lastVal) as the first argument. That value is also saved so that if you call set(newVal) again, it will only rerun get() if newVal is different than lastVal.

Setting a value to rerun the getter even though you aren’t using the set value seems odd because the set value will never be used except by what you are setting it to as a side effect. I think you want to replace this.attr('storage').get('loglevel') in your get() to just use the value passed into the getter.

There really are a lot of ways to do it, but you want to make sure you understand how all the change propagation works in order to move toward the right solution. I could probably help more if I could see more of your code.

thanks for the explanation, so far.

all properties in define:{} are implicitly bound, correct?

In my case of

var logger = services.getService('logger');
logger.debug('AppVM getting Sign VM');

debug is a void method, so why would it rerun currentPresentation.get() since the output of debug doesn’t change, even though it is calling this.attr('_logging') internally which did change state?

I would be happy to share more of my code with you and work this out further. What do you suggest? Also I am happy to spend more time learning how the change propagation works. I just haven’t seen much documentation on it, yet.

I tried this and it worked. Thanks.
I still need to figure out what to do about any method calling logger being rerun if I change the state of _logging or loglevel.

If the calling method is simply a Map method and not a define property, then it should not be bound and not rerun, correct?

Then I would just need to figure out how to rerun when the data I do want to observe gets called.
EDIT:
So in the log method I changes
if(!this.attr('_logging') || level > this.attr('currentLevel'))
to
if(!this._logging || level > this.currentLevel)

and now it doesn’t rerun the calling methods.

When currentPresentation.get() is run, all observables read during its execution will be bound, so even though logger.debug is just a method, it is reading logger.attr('showLog'). The framework keeps track of all this under the hood using attr() calls. That’s why a direct read to logger.showLog without attr() doesn’t trip observation. The only issue with using the direct read is it might not be supported in the future so it would be good to find a better way.

If your virtual properties must depend on properties that are likely to change more often than you want the virtual properties to be recomputed, then I would try to change them to non-virtual properties. You said you want to recompute/change logger properties when data is retrieved from the server. That would be a good opportunity to manually do the state change; for example, in a set().

If possible, sharing your modules in a JSBin or a Gist should suffice to giving me a better idea of what you are trying to do.

As for documentation, it is a little scattered here and there so I’m not sure there’s one good place that describes everything you want to know. The bread and butter of it all is observable objects (can.Map) and functions (can.compute). That is where all of the change propagation happens.