EmberJS: Using the in-element helper
Screenshot of the in-element helper API on EmberJS website

EmberJS: Using the in-element helper

Sometimes your team might have some use case to render some content somewhere else on the page. The content could be a loading screen, a modal, tooltip or even a dropdown. For these scenarios, my team used the ember-wormhole library. With the introduction of the in-element helper in EmberJS v.3.20, we wanted to try using that instead to reduce our library dependencies. In this article, I will compare some of the difference between ember-wormhole and the in-element helper.

You can also checkout the demo I created on github.

Here's a high level summary of the differences:

No alt text provided for this image

API differences

<EmberWormhole
  @to="ember-wormhole-destination"
>
  <div>I'm from ember-wormhole.</div>
  <label for="name">Name</label>
  <input type="text" id="name" name="name">
</EmberWormhole>

One of the most simple uses of ember-wormhole is specifying the id of the destination element you want to append your content to. Ember-wormhole is so resilient that you can place it in the route directly and the destination element can be anywhere in the dom. If the destination element does not exist, ember-wormhole will default to render in place. This makes the logic confusing and can lead to potential bugs, errors and unexpected behaviors. By using the in-element feature, we follow a more natural way of thinking where we think about the order of when things render. We need to ensure the destination element exists before we start attaching things to it by using it inside of components where we can control when to render our contents.

Below I have are code snippets of the component I built in the github example. This component is passed a variable of when to show the content, which has some text, a label and an input field. In js file, I define a getter called "destinationElement" where it finds the element I want my content to attach to.

import Component from '@glimmer/component';


export default class inputField extends Component {
  
  get destinationElement() {
    return document.getElementById('inElement-destination');
  }
}

The in-element feature takes in the "destinationElement" and renders my defined content to the element I defined.

{{#if @show}}
  {{#in-element this.destinationElement}}
  	<div>I'm from in-element.</div>
    <label for="name2">Name</label>
    <input type="text" id="name2" name="name2">
  {{/in-element}}
{{/if}}
 

In order for in-element to work, the element we feed it must be valid. During development it will throw errors if it doesn't but it will be silent in production as specified in its rfc.

Rendering in-place & state caching

There is no such thing as rendering in-place with the in-element feature. As mentioned, if the element we tell it to render to does not exist, the content will not render. If you want to mimic the render in-place behavior, you need to define the anchor element within your component and write the logic to switch between the anchor element and the other destination element.

gif of the content being re-rendered when using in-element

One thing to note though is that when the destination element changes, the content is re-rendered completely. For instance if your content has an input field and if you didn't cache the value before the destination element changes, the value is completely gone. If you used ember-wormhole, the content does not get re-render but re-attached to the new destination element.

Example 3: To append or not to append?

When you specify a destination to ember-wormhole, it appends the content to the destination. The in-element feature by default it replaces all the contents of the destination element. If you want to append you must specify it by adding "insertBefore=null". You must specify "null". It does not accept any other values.

No alt text provided for this image
    {{#in-element this.destinationElement insertBefore=null}}
      <div>I'm from in-element. I'm appending into the parent</div>
      <label for="name2">Name</label>
      <input type="text" id="name2" name="name2">
    {{/in-element}} 

Takeaways in using the in-element helper

  1. Understand when and where your destination element exists before using in-element. You may need to wrap it around a flag to determine when to attach your content.
  2. If the destination element changes, everything gets re-rendered. If you dynamically added css to the element or any attributes, they will be blown away unless you cache the information somewhere.
  3. By default in-element will replace all the contents of the destination element. You need to specifically use "insertBefore=null"
Nathaniel Furniss

Staff Software Engineer at LinkedIn

2y

Interesting, need to try out if it’s ignored. I ended up making a small wrapper around in-element to encapsulate some of this logic: https://github.com/nlfurniss/ember-simple-wormhole

Like
Reply
Nathaniel Furniss

Staff Software Engineer at LinkedIn

2y

How does getting the `destinationElement` work with FastBoot/BPR?

Like
Reply

To view or add a comment, sign in

Explore topics