DEV Community

Cover image for Ember.js in-element feature
shayan
shayan

Posted on

Ember.js in-element feature

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}}


Enter fullscreen mode Exit fullscreen mode

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 in insertBefore=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}}.

ember.js in-element new feature

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


Enter fullscreen mode Exit fullscreen mode

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.

ember.js in-element feature

So first of all update to new ember version



npm install -g ember-cli@3.20.0


Enter fullscreen mode Exit fullscreen mode

then create a new ember project



ember new emberjs-in-element-demo


Enter fullscreen mode Exit fullscreen mode

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>
...


Enter fullscreen mode Exit fullscreen mode

Then let create our desire component, and name it as render-out



ember g component render-out
ember g component-class render-out


Enter fullscreen mode Exit fullscreen mode

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')
}


Enter fullscreen mode Exit fullscreen mode

and use the new helper



{{!-- app/components/render-out.hbs -}}
{{#if @renderOut }} 
  {{#in-element this.myDestinationElement}}
    {{yield}}
  {{/in-element}}
{{else}}
  {{yield}}
{{/if}}


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

You can check my sample code in this repository.

Resources

Join the discussion

I would love to get some feedback here.

Top comments (2)

Collapse
 
baukereg profile image
Bauke Regnerus

A little late, but thanks for this blog. I just swapped ember-wormhole for in-element. The example with @renderOut was quite useful to replace wormhole's renderInPlace argument.

One thing I noticed is that using in-element can break tests, as assert.dom() doesn't seem to find the right element anymore. Since I use a component as abstraction to in-element, I enforce to render in place for tests.

get _renderInPlace() {
    const config = getOwner(this).resolveRegistration('config:environment');
    if (config.environment === 'test') {
        return true;
    }
    return this.args.renderInPlace;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jeffdaley profile image
Jeff Daley • Edited

This might depend on your renderOut target. If you use document.body, your modals will render outside the QUnit container and fail await find() references. If you use document.querySelector('.ember-application'), you should be good (at least that's the case for me).