It's Possible!
In a previous version of this answer, I stated that this was not possible. After working with some Lightning Developers at Salesforce (many thanks, JF & Jaswinder), we were able to work out how to get Lightning components to work with a component's custom renderer.
Note that this is not exactly canon, and we're working on the edge (maybe slightly beyond) of what's supported, so if you go this route, you are accepting the responsibility to maintain it in the future if some low-level change in Lightning suddenly breaks everything. Then again... isn't that the nature of software development? 😉
The example below was built with API version 41.
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="flexipage:availableForAllPageTypes" access="global" >
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<div aura:id="root"></div>
<div aura:id="renderBox" style="display: none;"></div>
</aura:component>
Helper
({
createComponent: function(component, componentType, componentAttributes, targetElement){
$A.getCallback(function(){
$A.createComponent(
"c:wrapper",
{
componentType: componentType,
componentAttributes: componentAttributes,
targetElement: targetElement,
},
function(wrapperComponent, status, errorMessage){
if (status === "INCOMPLETE") {
return;
}
else if (status === "ERROR") {
return;
}
var renderBox = component.find("renderBox");
var body = renderBox.get("v.body") || [];
body.push(wrapperComponent);
renderBox.set("v.body", body);
}
);
})();
},
})
Renderer
({
'afterRender' : function(component, helper) {
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 );
// ************************************************************
// This is the key line of code. I've created DOM elements
// above, and now I'm going to render a Lightning component
// and attach it to those DOM elements:
helper.createComponent(
// A reference to the parent component:
component,
// The type of Lightning component to render:
'forceChatter:publisher',
// Attributes to send to the Lightning component to render:
{ context: "GLOBAL", type: "News" },
// The DOM element where the Lightning component should
// ultimately live:
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 >
<!-- Describes the component that we want to render -->
<aura:attribute name="componentType" type="String"/>
<aura:attribute name="componentAttributes" type="Object"/>
<!-- The target element where the component should be moved -->
<aura:attribute name="targetElement" type="Object"/>
<!-- This is where our component is initially rendered, and it's where the component "lives" in Lightning's DOM -->
<div aura:id="holdingPen"></div>
<aura:handler name="init" value="{!this}" action="{!c.init}"/>
</aura:component>
Controller
({
init: function(component) {
$A.createComponent(
component.type = component.get("v.componentType"),
component.attributes = component.get("v.componentAttributes"),
function(cmp){
component.find("holdingPen").set("v.body", [cmp]);
}
);
}
})
Renderer
({
afterRender: function(cmp) {
if (cmp.moveComplete) return;
var holdingPen = cmp.getElement(),
target = cmp.get("v.targetElement");
if (target && target.appendChild){
target.appendChild(holdingPen);
cmp.moveComplete = true;
}
}
})
Result
It works! Of course, there are still some issues here that need to be addressed. For example, you are now responsible for handling the dynamically generated component's lifecycle. If you decide to remove it or it's parent from the DOM, then you need to explicitly call destroy on the dynamic component. How exactly you handle that is going to depend on your architecture and isn't covered by the example above.
Here's a screenshot of my decidedly un-lovely page:
Best Answer
In Component initialization child component Init event is fired before parent Init event.Use
<aura:if>
tag to check all required attributes before creating component.