In this article, I am going to talk about a new feature of Ember.js on portal rendering which comes in V 3.20. Also, I am going to build a simple sample application using this feature. For more, let’s stay tuned.
Single-page applications use to load and render their code under one root DOM element, which brings a limitation for the developers. Say you need to render an element into your application. Easy right? That element is mounted to the nearest DOM element and rendered inside of it as a result. But! What if we want to render that element outside of the div somewhere else? That could be tricky because it breaks the convention that a component needs to render as a new element and follow a parent-child hierarchy. The parent wants to go where its child goes.
In a library like React.js developers can use ReactDOM.createPortal(child, container)
to achieve this goal. In the Ember.js framework, developers could use community addons like ember-wormhole or ember-elsewhere.
Ember.js recently released a new version 3.20 which introduces a new helper for this approach. It’s {{in-element}}
.
{{#in-element this.myDestinationElement}}
<div>Some content</div>
{{/in-element}}
To use the helper, pass in a DOM element to target (this.myDestinationElement
in the example below) and a block to render:
This new public API behaves a little differently from the private API:
- For the public API
{{in-element}}
, by default, the rendered content will replace all the content of the destination, effectively becoming its innerHTML. If you want it to be appended instead of replacing the content, you can pass ininsertBefore=null
. - In the private API
{{-in-element}}
, the rendered content was appended to any existing content in the destination. Developers should use the public API,{{in-element}}
, and discontinue using{{-in-element}}
.
For using this helper, first, you need to update your ember to version 3.20. Try below command then
npm install -g ember-cli@3.20.0
And then you can create a new ember project and use this feature.
Note: By default, Ember renders the main application after all body elements, and appends the directory to <body>
. So, if you suppose to render the outer element below your vendor resources file, it doesn’t work. As a result, if you suppose to render your outer elements, below your main application element, you should specify the rootElement
of your Ember app located in PATH-TO-PROJECT/app/app.js
and then you can add your outer element below that.
Sample Use-case
To create a simple use-case of this new feature let create a really simple sample.
So first of all update to new ember version
npm install -g ember-cli@3.20.0
then create a new ember project
ember new emberjs-in-element-demo
So, then let create our outer element. for that you should edit PROJECT/app/index.html
file as below
...
<body>
{{content-for "body"}}
+ <div id="modal_root"></div>
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/emberjs-in-element-demo.js"></script>
{{content-for "body-footer"}}
</body>
...
Then let create our desire component, and name it as render-out
ember g component render-out
ember g component-class render-out
Then we should specify our destination element in the component.
// app/components/render-out.js
import Component from '@glimmer/component';
export default class RenderOutComponent extends Component {
myDestinationElement = document.getElementById('modal_root')
}
and use the new helper
{{!-- app/components/render-out.hbs -}}
{{#if @renderOut }}
{{#in-element this.myDestinationElement}}
{{yield}}
{{/in-element}}
{{else}}
{{yield}}
{{/if}}
To explain the above code, if a user passed the @renderOut
property, then the passed children would render in the <div id="modal_root"></div>
element, otherwise, it renders as before.
So you can use it as
{{!-- app/templates/application.hbs -}}
<RenderOut @renderOut=true>
This content render inside of <div id="modal_root"></div>
</RenderOut>
You can check my sample code in this repository.
Resources
- https://blog.emberjs.com/2020/07/29/ember-3-20-released.html
- https://github.com/shayanypn/emberjs-in-element-demo
- https://reactjs.org/docs/portals.html
Join the discussion
I would love to get some feedback here.
Top comments (2)
A little late, but thanks for this blog. I just swapped
ember-wormhole
forin-element
. The example with@renderOut
was quite useful to replace wormhole'srenderInPlace
argument.One thing I noticed is that using
in-element
can break tests, asassert.dom()
doesn't seem to find the right element anymore. Since I use a component as abstraction toin-element
, I enforce to render in place for tests.This might depend on your
renderOut
target. If you usedocument.body
, your modals will render outside the QUnit container and failawait find()
references. If you usedocument.querySelector('.ember-application')
, you should be good (at least that's the case for me).