Question on why inheritBindingContext is off for normal component, but on for dynamically composed component


#1

I was tracing through Aurelia source code trying to understand the different behavior on accessing binding scope in views created by <compose view-model="./child"></compose> and <child></child>.

The dynamically composed view can see the view-model of parent view through overrideContext chain, but the statically created <child></child> can not access view-model of parent view.

Which leads me here:

I then saw in instructions.js, the inheritBindingContext flag is true only for dynamically composed view (either created by compose tag or loaded by router).

From the comments in controller.js showing above, it sounds like it was not a fully rejected idea to turn on inheritBindingContext for all components regardless.

May I ask why it’s not turned on by default yet?

It is a nice feature to have, so that I only need to define currencyCode/locale/timeZone in top level app.js, and then just use them in all child view html template without explicitly bind them every level down.

For the meantime, for anyone interested in how to turn it on on certain component.

@processContent((compiler, resources, node, instruction) => {
  instruction.inheritBindingContext = true;
  return true;
})
export class ChildComponent {/*...*/}

If this sounds confusing to you, (it probably does), this gist shows you what this can do.


#2

Another way to get the parent’s context:

bind( bindingContext ) {
    this.parent = bindingContext; // bindingContext is the parent view-model
}

IMHO, automatically giving access to all parent binding contexts up the chain will pollute the namespaces of child elements. It would be better to place currencyCode/locale/timeZone type information in their own classes and inject them only into the elements that need them.


#3

@jeffgrann, thanks for your thought. Injection is exactly what I am using. But I want to use binding scope magic, I do not view it as pollution as long as we don’t abuse it.

I saw you edited your bind() example, you have noticed what I noticed before. The bindingContext is not the current component, but the outer component. This is against our impression, right? If you wondered how the html template gets its binding working against current component with bindingContext pointing one level up. The view actually binds to different scope than the component (implemented in Aurelia View class).

All the above is just for statically created component. For dynamically composed component, the picture is different. The bindingContext in your bind() callback is indeed the current component, and overrideContext points to outer level bindingContext.

Since a component doesn’t know how it will be used, this is not a reliable way to peek overrideContext/bindingContext in the bind() callback. Furthermore, you component could behind a repeater which introduces another layer of overrideContext, that’s also out of your component’s control.

Explicitly bind stuff to child component is reliable since the value passed in was controlled by owning view, not the child component itself.

Injection is another way to share information, but it is not context aware. For instance, I want to use a child component in two parts of my screen, one to display invoice in AUD, another one to display invoice in USD. Injection doesn’t work here since it doesn’t know where the component will be used. We have to use explicit binding or rely on inheritBindingContext.

I view inheritBindingContext is better choice in the above scenario. In top level app.js, I can put currencyCode to “AUD” as default. In some part of the view hierarchy I can easily override the currencyCode to “USD”. My child component will just work depending on where it is added to the view.


#4

It was raised once before. https://github.com/aurelia/templating-resources/issues/222

Like Rob said, it needs to be there to encapsulate normal custom element. The framework would be unusable otherwise


#5

Thanks very much for the information.

To sum it up, from what I understand by reading the issue:

  1. The inheritBindingContext should be off by default for all cases.
  2. It was left on for dynamically composed component, only because Aurelia does not want to break legacy apps.

I understand accessing information through binding scope chain is like accessing global variables, which should be avoid. It’s bit a shame as I feel the binding scope was beautifully designed to match view hierarchy, which I want to take advantages of.

I would still like to experiment it in some limited cases for merely display something like time or currency code. I would like to see the purposed attribute inherit-binding-context being implemented not only for compose tag but for all custom component tag, so I can turn it on like <child inherit-binding-context></child> without using verbose @processContent.