7

Before I start, I'm not referring to <aura:template /> for bootstrapping the Aura framework. That's something different. What I mean is I would like to create a "template" to use for generating components at runtime. The ComponentDefRef type seems to be the way to do this, but there isn't much in the way of documentation.

Take a look at <aura:if />. It has two attributes, body and else, of type ComponentDefRef[]. Internally, it takes these attribute values and generates components from them and inserts the components into the app. You can look at the source here. <aura:iteration /> works in a similar way. Its body is of type ComponentDefRef[] and is used to generate a list of components.

I would like to create a Lightning component which does something similar. I would like to take a ComponentDefRef[], turn it into a Component[], and insert it into the component hierarchy for rendering. I've struggled with this for a few days with no luck.

As an example, take the app and component below. When I try to run it, Salesforce returns an error:

An internal server error has occurred Error ID: 1176784454-8959 (-227057212)

Keep in mind this is a contrived example to illustrate what I want to do. What I'm trying to build is a grid control.

testTemplate.cmp

<aura:component>
    <aura:attribute name="var" type="String" />
    <aura:attribute name="indexVar" type="String" />
    <aura:attribute name="items" type="Object[]" />
    <aura:attribute name="template" type="Aura.ComponentDefRef[]" />

    <aura:iteration items="{!v.items}" var="{!v.var}" indexVar="{!v.indexVar}" template="{!v.template}" />
</aura:component>

tester.app

<aura:application extends="force:slds">
    <aura:attribute name="countries" type="Object[]" />

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <div class="slds">
        <div class="slds-grid">
            <div class="slds-col slds-p-around--small">
                <c:testTemplate items="{!v.countries}" var="country" indexVar="i">
                    <aura:set attribute="template">
                        <div>{!i}. The capital of {!country.name} is {!country.capital}.</div>
                    </aura:set>
                </c:testTemplate>
            </div>
        </div>
    </div>
</aura:application>

testerController.js

({
    doInit: function(component, event, helper) {
        var countries = [{
            name: 'France',
            capital: 'Paris'
        }, {
            name: 'Britian',
            capital: 'London'       
        }, {
            name: 'Spain',
            capital: 'Madrid'       
        }];

        component.set('v.countries', countries);
    }
})
2
  • I edited the original code samples to correct the internal server error.
    – Joe B
    Dec 2, 2016 at 14:56
  • Sorry for reviving an old post here. I just tried this code and it seems to work with locker service, despite the APIs not being specifically exposed. Do you still use this functionality? Do you fear it's future-proofing?
    – wellmstein
    Aug 9, 2017 at 16:00

2 Answers 2

4

I face similar issues in my current project. I'm trying to create a datatable implementation with columns defined by inner column components in a following matter:

    <c:datatable items="[...]" var="item">

        <c:column>

            <aura:set attribute="header">
                Column Header
            </aura:set>

           Column Body:{!item}
        </c:column>

   </c:datatable>

Now what I've discovered is that you can use Aura.componentdefref[] attribute type to define iteration template and then set this template like any other facet, using attribute:set. Please find an example below:

<aura:component>
    <aura:attribute name="template" type="Aura.componentdefref[]"></aura:attribute>
    <aura:attribute name="var" type="String" ></aura:attribute>
    <aura:attribute name="items" type="Object[]" ></aura:attribute>

    <aura:iteration items="{!v.items}" var="{!v.var}" template="{!v.template}" >
    <!-- Content doesnt matter but it cannot be empty -->
        i 
    </aura:iteration>

</aura:component>

Then you can use component like this:

<c:list var="item" items="[1,2,3,4]">
    <aura:set attribute="template">
        Test template {!item}
    </aura:set>
</c:list>

Output:

Test template 1 Test template 2 Test template 3 Test template 4

There might be way to transform component's body into a template, but I had no luck so far. I hope this information can be of any use to you. It's really hard to find any information on this matter.

2
  • You got it! I was able to use your code to figure out what I was doing wrong. I had to change two things. One, I misspelled Aura.ComponentDefRef. Two, I was setting the template in the body rather than using the template attribute. Fixing both of these problems got it working. I've updated the code samples in the original question.
    – Joe B
    Dec 1, 2016 at 23:59
  • One more thing to note. I was able to get my component to work without a content body in <aura:iteration>.
    – Joe B
    Dec 2, 2016 at 0:00
3

To unblock you really quick, Aura.ComponentRefDef is not a thing in lightning.

change

<aura:attribute name="template" type="Aura.ComponentRefDef[]" />

to

<aura:attribute name="template" type="Aura.Component[]" />

in general Internal server error often occure when lightning does not understand the type of the attribute. for example if attribute is of type "String" all good, but if you change it to "text" you will get internal server error. This is a bug, the unrecognized type should not allow you to save the code, but it is what it is.

To analyze your code deeper. if you want to iterate over many element inside your components use {!v.body} facet (this is array of Aura.Component btw) to access original body and then use another facet, for example {!v.modifiedBody} to show the modified bodies. This should work for static html without expressions.

If you will try to pass some expressions in the body - you might run into "value providers" issue. for your example {!country} is not a registered value provider.

8
  • using lightning expressions limit you to the value providers that lightning gives you. I don't know documented way to extend it. What i would do in your situation - i would use some other tokens instead of lightning expressions, pass dynamic data as attributes and substitute prepared template tokens with real values. This is the best that i can offer. But I am curious if you find better solution, lightning native solution.
    – Pavlonator
    Nov 9, 2016 at 21:54
  • This doesn't work. I get an error reading, "No value provider was found for 'country'." I tried searching for a way to create and apply a custom value provider to a component with no luck.
    – Joe B
    Nov 9, 2016 at 21:54
  • Pavlonator - thanks for your insight and comments. I think you're right. Lightning is limited in this regard. This is frustrating because I want to create dynamic and reusable components using the concept of templates to stamp out new components at runtime. Aura can do it but not Lightning.
    – Joe B
    Nov 9, 2016 at 21:58
  • 1
    Sorry that was a bit naughty of me to suggest ;) - just was frustrated because I spent a few hours building a table component that I had intended to work using templates... sigh. Never mind, I'll just pass in a list of component names in an array to correspond to the data columns. Or even pass in the names in as an attribute on the header object array. You are doing good work and I like the framework, btw. @KrisGray May 20, 2017 at 2:39
  • 1
    No problem at all Caspar. I appreciate the feedback on what features you're interested in. Some features we use internally and they work, but are bad APIs and need cleanup before exposing. We just have other priorities right now than exposing some of the more lightly used features.
    – Kris Gray
    May 20, 2017 at 20:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .