2 of 4
added 215 characters in body
JDB
  • 670
  • 6
  • 20

I spent about 3 days on this and have come to the simple conclusion that this is not possible in Locker Service (API version 40.0 and above).

Once you've created a DOM element (via document.createElement) you can never have a Lightning Component as a child of that element. Once you go DOM, you can never go back.

Here's what I tried in order to test this theory:

Container Component

The container component is the component that has my manually manipulated DOM elements. It's where I want to display other Lightning Components.

Component XML

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,forceCommunity:availableForAllPageTypes,force:hasRecordId" access="global">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <div aura:id="root"></div>
    
    <div aura:id="renderBox"></div>
    
</aura:component>

Controller

({
    doInit : function(component, event, helper) {
        helper.createComponent(component, [
            "lightning:inputRichText",
            {
                "aura:id": "dynamicComponent"
            }
        ]);
    },
})

Helper

({
    createComponent: function(parentComponent, childComponentDef){
        var wrapperDef = [ "c:dynamicWrapper", { "aura:id": "dynamicWrapper" } ],
            components = [ wrapperDef, childComponentDef ];
        
        $A.createComponents(components,renderNewComponent);
        
        function renderNewComponent( newComponents, status ){
            if (status !== "SUCCESS") return;
            
            var wrapper = newComponents[0];
            
            wrapper.set('v.children', newComponents[1]);
            wrapper.addEventHandler('ready', function(){
                var wrapperElement = wrapper.getElement(),
                    renderBoxElement = parentComponent.find('renderBox').getElement();
                parentComponent.mainElement.appendChild( renderBoxElement.removeChild( wrapperElement ) );
            });
            parentComponent.find('renderBox').set('v.body', wrapper);
        }
    },
})

Renderer

({
    'render' : function(component) {
        return this.superRender();
    },
    'afterRender' : function(component) {
        var mainElement = component.mainElement = document.createElement('div'),
            span1Element = document.createElement('span'),
            span2Element = document.createElement('span');
        
        mainElement.style.border = 'solid red 8px';
        mainElement.style.margin = '50px 20px 120px 90px';
        mainElement.style.padding = '20px 130px 90px 10px';
        mainElement.style.backgroundColor = 'aqua';

        span1Element.innerText = 'I am not a ';
        span2Element.innerText = 'Lightning Component';
        span2Element.style.fontWeight = 'bold';
        
        mainElement.appendChild( span1Element );
        mainElement.appendChild( span2Element );
        
        component.find('root').getElement().appendChild( mainElement );
    }
})

##Wrapper Component

The documentation is pretty clear that you cannot access the getElement() function of a component from another namespace, but I wondered if I might be able to wrap a Lightning Component within a wrapper component that's in my namespace. This would give me access to the element and might allow me to move things around. I could render the desired component within the "renderBox" div and then move it into my custom DOM.

Component XML

<aura:component >
    
    <aura:registerEvent name="ready" type="c:dynamicWrapperReady" />
    
    <aura:attribute name="children" access="global" type="Aura.Component[]"/>
    
    <aura:handler name="render" value="{!this}" action="{!c.fireReadyEvent}" />
    
    {!v.children}
    
</aura:component>

Controller

({
    fireReadyEvent : function(component) {
        var e = component.getEvent("ready");
        e.fire();
    }
})

##Ready Event

I needed a way to know when the wrapper component had finished rendering so that I could actually get it's element (otherwise it simply returned null).

Event XML

<aura:event type="COMPONENT" description="The dynamic wrapper is ready for use." />

Result

Everything works perfectly until this line in the Container Component's Helper:

renderBoxElement.removeChild( wrapperElement );

At this point, if your component is using API version 40.0 or later (in other words, Locker Service is turned on), you get the following JS console error:

aura_prod.js:8 Uncaught JH {component: "namespace:dynamicWrapper$controller$fireReadyEvent", componentStack: "[one:one]>[one:standardLayoutContainer]>[force:con…ustomRendererWithChildren]>[namespace:dynamicWrapper]", action: null, name: "Error", message: "Uncaught Action failed: namespace:dynamicWrapper$contr…vascript/ra1ulv_rPNMd9ZOOtLxgew/aura_prod.js:8:15", …}

Note that everything above works perfectly in API version 39.0, the version before Locker Service was turned on by default for Lightning Components. This is not an issue with Aura, just Lightning Locker Service.

JDB
  • 670
  • 6
  • 20