The Path to Contextual Components: Contextual Components

Contextual components are introduced in Ember.js 2.3 to improve component composition and the usage of components for DSL.

Contextual components bring three new features to Ember:

  1. The hash helper: this simple helper just returns a hash with the attributes passed to it.
  2. Closure components: similar to closure actions, a closure component encapsulates a component with a set of attributes for later reuse.
  3. Dot syntax: any identifier with a dot (.) whose context is a closure component will be rendered as the component itself.

Using the hash helper

The hash helper, though simple, is a fundamental element of this new feature as we will see in the third part. Invoking the hash helper with a collection of key-value pairs returns a hash with the given collection as content.

{{! app/templates/components/my-hash.hbs }}
{{yield (hash text="my-text-component"
checkbox="my-checkbox-component"
textarea="my-textarea")}}

This component will yield a hash with the passed key-value pairs. The following template:

{{#my-hash as |f|}}
{{f.text}}
{{f.checkbox}}
{{f.textarea}}
{{/my-hash}}

would produce three lines with the names of the components: my-text-component, my-checkbox-component, and my-textarea.

Twiddle

Creating a closure component

The first part of using contextual component is the ability to create a closure around a component and a set of parameters and attributes. The {{component}} helper has been modified to be able to render these closures too.

Creating a new contextual component is very easy: you just need to use the component helper!

Let us create our first contextual component:

{{! app/templates/components/my-greeter.hbs }}

{{yield (component (if formal "formal-greeting" "informal-greeting") name=name)}}
{{! app/templates/components/formal-greeting.hbs }}

Good morning, Mr/Mrs {{name}}!
{{! app/templates/components/informal-greeting.hbs }}
Hey, {{name}}, how's it going?

Then, we can render our closure using the component helper as in the following snippet:

{{#my-greeter formal=true name="Sergio" as |greet|}}
{{component greet}}
{{component greet name="Matthew"}}
{{/my-greeter}}

In my-greeter, (component ...) creates a closure over the inline if and name="Sergio". This closure is passed as first argument to the block. Then, when rendered with {{component greet}} would be like invoking it as {{component "formal-greeting" name="Sergio"}}. In the second example, the passed name is Matthew, the most recent value to the name attribute.

As with the usual {{component}} helper, the rendered component can change just by changing the first parameter to the helper. You can play with this twiddle to see basic contextual components in action.

Rendering a closure component without the component helper

Remember when I said that the hash helper, though simple, is a fundamental element of contextual components? Well, this is the part everything comes together. The best part of contextual components is that, if Ember finds and invocation with a point in the name containing a closure component, it will render the component. As an example:

{{! app/templates/components/form-for.hbs }}

{{yield (hash text-field=(component "my-text-field" object=object)
text-area =(component "my-text-area" object=object)
checkbox =(component "checkbox" object=object))}}
import Ember from 'ember';

const FormForComponent = Ember.Component.extend({});

FormForComponent.reopenClass({
positionalParams: ['object']
});

export default FormForComponent;

The FormForComponent is quite simple. It has a positional parameter mapped to object (just not to call it model) and exposes a hash with three different components. Then, we can invoke those components by using the dot syntax for contextual components:

{{! app/templates/your-route.hbs }}

{{#form-for model as |f|}}

{{f.text-field "name"}}
{{f.text-area "lastName"}}
{{f.checkbox "lovesEmber"}}

{{/form-for}}

When Ember finds a dot in the first part of a moustache, it checks if the value is a contextual component. If it is, Ember is intelligent enough to unbox it and render it.

Full example

Previous example needs some more code to be fully functional. The rest of the code can be found in this twiddle. There is an interesting trick being done there: you can use the mut helper with a get helper inside to make a mutable binding to a variable property of an object.

You can see the code in this gist as well.

Gotchas

  1. While passing null or undefined as component name to the component helper will render nothing, passing it to a closure component will raise an error.
  2. Conflicting positional parameters and attributes usually raise an assertion. When working with contextual components, a collision between current parameters and attributes with those from the closure won’t raise an error.

Author: Serabe

Mathematician, and Ruby and JavaScript programmer. Sometimes I speak at conferences and local meetups.

9 thoughts on “The Path to Contextual Components: Contextual Components”

  1. Hi !

    Thanks for this article. I have a small question. I want to use the component helper to pass arbitrary parameters:

    {{component name myParameters}}

    where myParameters is an object {foo: bar, bar: baz}. However it seems it does not work and I’m force to write the parameters this way:

    {{component name foo=myParameters.foo bar=myParameters.bar}}

    But in my case this is not viable a I can’t know the parameters needed. Is there any way to achieve that?

    Thansk!

  2. Is there any way around that first gotcha that you mention? My component is yielding a hash containing multiple closure components, but sometimes my consuming template uses only one of those closure components, and I get the following error (which I’m assuming is the one you’re referring to: `A helper named “my-yielding-component.some-closure-component” could not be found`.

  3. Hello Sheldon,

    the problem you are having seems like Ember cannot find anything in `my-yielding-component.some-closure-component`. Can you try to reproduce the error in an Ember Twiddle?

    In case you ever face the first gotcha, the way to solve it is having a default component to render and making sure that the component name passed is either the one received dinamically somehow or the default one.

    Thank you!

  4. Hey Serabe!

    Do you have any idea where you will publish this article (or if you could give me a small hint on how to solve the problem I was talking?). I desperately try to find a workaround around this :(.

  5. It’d be nice if you could explain the motivation behind this feature (that is, the problem it solves).

Leave a Reply

Your email address will not be published. Required fields are marked *