Your Portal Content through a LitElement
Tags: webcomponents, shadowdom, html, javascript
Published: 2019-07-12 02:01:07

---
title: Your Portal Content through a LitElement
published: true
description: Now that we can build portal content with shadow DOM and custom elements, let's take advantage of LitElement to make the process even easier!
tags: web components, shadow dom, HTML, javascript 
---

![](https://thepracticaldev.s3.amazonaws.com/i/c7x8ty8v9waf8serhpii.png)

[When last we met](https://dev.to/westbrook/your-content-in-shadow-dom-portals-3cdb) we were sending content through [portals](https://reactjs.org/docs/portals.html) as if we were [Jack O'Neill](https://en.wikipedia.org/wiki/Jack_O%27Neill) sending soldiers to other galaxies. Not only that, we were doing it with vanilla javascript, thanks to the help of [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) and [Custom Events](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent), rather than with a framework as had some of the solid examples of [prior art](https://www.npmjs.com/package/portal-vue) that we checked out. If you haven't had the pleasure, or have forgotten much of what you read (join the club), don't worry, the rest of us will wait here for you...

...[Your Content in Shadow DOM Portals](https://dev.to/westbrook/your-content-in-shadow-dom-portals-3cdb)...

...ok, now that we're all ready, there's no reason to bury the lede, today we're gonna talk about what those same techniques look like when taking advantage of the support of a simple base class for creating fast, lightweight web components; [LitElement](https://lit-element.polymer-project.org/). And so, without further ado, here's what that looks like it all of its glory:

{% stackblitz lit-element-portal view=preview %}

Well, maybe not _all_ of its glory, more like in its one-to-one porting of the realities discussed and delivered with fully vanilla JS in the previous article. We've seen some of it before in the [_Declarative API_](https://dev.to/westbrook/your-content-in-shadow-dom-portals-3cdb#declarative-api) section of the previous article, but it's important to revisit it now as it will form the basis for extending the elements to support the ideas discussed across the whole [_But, now what?_](https://dev.to/westbrook/your-content-in-shadow-dom-portals-3cdb#but-now-what) section therein. So, let's work up to full glory together!

# Cross Browser Support

This was a large piece of any production possible code that I chose to leave out of our previous conversation for proof of concept's sake. [We discussed some of the possibilities](https://dev.to/westbrook/your-content-in-shadow-dom-portals-3cdb#cross-browser-support) but didn't get into them, until now. The first place we'll run into an issue is with the use of `slot.assignedNodes()`. You may remember we had previously been using `slot.assignedElements()`, however, we want to be able to get loose text nodes as well as elements, so `assignedNodes` is the way to go. Let's take a look at what the code relying on this looks like now:

```js
    projectSlot(e) {
        if (!e.target.assignedNodes().length) return;
        this.dispatchEvent(createEvent('portal-open', {
            destination: this.destination,
            content: e.target.assignedNodes(),
        }));
    }
```

You may also remember that when relying on ShadyDOM in a polyfilled setting there is no support for `assignedNodes`, so we'll need to do some extra work in order to enable the same functionality cross-browser. How sad that literally two lines of code charge such a tax on our goals here, but not to worry we can access similar results in this context with via `[...el.childNodes]`.  While in _most_ cases this would do this trick, because of the use of a `<slot />` tag with no `name` attribute we need to filter out a few possibly false positives before passing content on to our `<portal-destination />`.

```js
    get portalContent() {
        const slot = this.shadowRoot.querySelector('slot');
        return slot && slot.assignedNodes
            ? slot.assignedNodes()
            : this.childrenWithoutSlots;
    }
    get childrenWithoutSlots() {
        let nodes = [...(this.childNodes.length ? this.childNodes : [])];
        nodes = nodes.filter(
            node => node.slot === '' || node.slot === null
        );
        return nodes;
    }
    projectSlot() {
        let content = this.portalContent;
        if (!content.length) return;
        this.dispatchEvent(createEvent('portal-open', {
            destination: this.destination,
            content: content,
        }));
    }
```

If you're interested in following along with the above code in real life, there are several ways you can access older browsers. The nuclear option is working with tools like [BrowserStack](https://www.browserstack.com/), or you could rely on one of the Virtual Machines that Microsoft offers for [various versions of Internet Explorer and Edge](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/), but my current go-to is [Firefox: Extended Support Release](https://www.mozilla.org/en-US/firefox/organizations/). Firefox ESR is an enterprise-targeted release of the Firefox that is currently shipping version 60 which initially released before the v1 web components specification was supported by Firefox. It doesn't make debugging very fun, as I've not figured out how to open the dev tools, however `alert()` works just fine and I've leveraged it more than I'd like to admit...

In the realm of cross-browser support, the remaining context for us to cover is applying styles to the content when it reaches the destination end of the portal. This is really where things get tricky and force us to weigh the pros and cons of various paths forward. By default `LitElement` will do the work of ensuring the ShadyCSS is applied to components in a polyfilled context. ShadyCSS does the work to emulate shadow DOM based style encapsulation in browsers that do not yet support the specification natively, a list of browsers that grows shorter every day with the sun settings on IE11 and pre-Edgium Edge. It does so at the intersection of correctness and performance by writing a single scoped version of the styles targeted to the component in question into the global scope. This goes a long way towards maintaining the "styles scoped to element" contract of Shadow DOM based styles; however, it comes with two main trade-offs. The first involves not specifically addressing the "protected from external selectors" contract, which means that ALL styles from outside of your shadow DOM will have the ability to leak into your component. The second is more particularly troubling in the context of our `portal-destination` definition, the styles applied to all instances of custom element's shadow DOM will have to be the same by default.

In that each piece of projected content over the lifecycle of an application _could_ be deserving of custom styling this can prove tricky in the context we've been working so far where we apply our content directly to the `<portal-entrace/>` element:

```html
    <portal-entrance destination="style-demo">
        <style>button{background: red;}</style>
        <h1>Send This Content</h1>
        <p>Hello world! From my-element ${this.counter}</p>
        <button @click=${this.increase}>+1</button>
    </portal-entrance>
```

For the `<style/>`s defined in this context to apply to the `portal-destination` element, we need to do work over the top of the `LitElement` implementation to correctly scope this content via the ShadyCSS polyfill. What's more, the `<style/>` element would need to not be inside of the `shadowRoot` of a parent element at runtime to ensure it will not be consumed for by that parent element as if those styles were meant for it. The most direct way to overcome this issue is to wrap the content that we'd like to send over the portal in a custom element:

```html
    <portal-entrance destination="destination">
        <content-to-be-ported-element></content-to-be-ported-element>
    </portal-entrance>
```

However, the restrictions this places on potential use are quite prohibitive:

1. Don't put `<style/>` elements directly into you `<portal-entrance/>`'s light DOM.
2. When attempting to send style data across the portal, wrap it in an element, a la `<content-to-be-ported-element/>`.
3. Manually wire state management between the parent element and the `<content-to-be-ported-element/>`.
4. Etc.

While every well-defined piece of code requires a list of things you can't do with it, I feel this is a bridge too far. We should be able to dial these back a bit and allow us to ship this functionality with a little more flexibility. The main thing we are looking to address here is the ability to place `<style/>` elements directly into the `<portal-entrance/>` element and have those styles apply to the `<portal-destination/>` element to which they are sent. Luckily, whether you are using `@webcomponents/webcomponentsjs/webcomponents-bundle.js` or its slimmed down younger sibling `@webcomponents/webcomponentsjs/webcomponents-loader.js` to ensure cross-browser support they will each ensure that browsers without native shadow DOM support are delivered the ShadyCSS polyfill.

The ShadyCSS polyfill supplies an API by which templates and styles can be prepared to approximate the encapsulation of the content in our similarly polyfilled shadow root from the rest of the document. We can use it to do additional work of it over the top of what is provided by `LitElement` in order to ensure the same treatment of `<style/>` content sent over our portal. The process involves these steps:

1. Ensure that either the polyfill is not present or that it is not presently being used. 
2. Create a template to prepare the styles in.
3. Gather all of the `<style/>` tags that will be direct children on the `<portal-destination/>` element. Capture both their style text (`innerHTML`) for scoping and append the nodes to the template created above for preparing the DOM.
4. Early return if no `<style/>` tags have been found.
5. Use ShadyCSS to scope the gathered CSS text and prepare the template to apply those scoped styles.
6. Forward the non-HTMLStyleElement elements to be appended to the `<portal-destination/>` element.

This looks like the following in code:

```js
    get preparedProjected() {
        if (!this.projected) return [];
        if (
             window.ShadyCSS === undefined ||
             window.ShadyCSS.nativeShadow
        ) {
            return this.projected;
        }
        let styles = [];
        let template = document.createElement('template');
        this.projected
            .filter(el => el.constructor === HTMLStyleElement)
            .map((s) => {
                styles.push(s.innerHTML);
                template.appendChild(s);
            });
        if (styles.length === 0) {
            return this.projected;
        }
        template.innerHTML = stylesHTML.join('');
        window.ShadyCSS.ScopingShim.prepareAdoptedCssText(
            styles, this.localName);
        window.ShadyCSS.prepareTemplate(template, this.localName);
        window.ShadyCSS.styleElement(this);
        return this.projected
            .filter(el => el.constructor !== HTMLStyleElement);
    }
```

This means that our usage caveats are much more acceptable:

1. You cannot have `<style/>` element openly available for consumption by a parent component at runtime.
2. Only `<style/>` elements that are direct children will apply to the light DOM content of an "entrance".
3. `<style/>` elements directly in the `<portal-entrance/>` light DOM will apply to all `<portal-destintion/>` elements and their content, regardless of `name`.

> Note: I would expect styles applied this way to _rougly_ follow the cascade (it seems they get added in reverse cascade 😖, but the current state of the ShadyCSS library does not agree. I've created the following [issue](https://github.com/webcomponents/polyfills/issues/156) to hopefully bring some agreement between the expectation and the reality here.

With these alterations, our family of portal elements is now ready for delivery cross-browser no matter the level of support those browsers have for the Shadow DOM specification. This capability came with some active trade-offs, but as they are directly in line with those that come with the ShadyCSS polyfill itself, which means they will hopefully be familiar to those working with other web components and shadow DOM tools.

When you bring this all together in an updated version of our [_Menu Populates Content Populates Menu_](https://dev.to/westbrook/your-content-in-shadow-dom-portals-3cdb#menu-populates-content-populates-menu) Example from the previous article, it looks like the following in all its cross-browser supporting glory:

{% glitch dirt-fish-1 app %} <figcaption>A close eye might catch the use of `no-shadow` on the `<html-include-with-anchors/>` elements, there is some sort of timing issue in upgrading there `<portal-entrance/>` elements therein that I'm gonna have to follow up on separately.</figcaption>

From this baseline, we can now focus on rounding out some of the capabilities of our portal.

# Declarative API

The ability to dynamically track the attributes of an element without any special APIs for setup is certainly one of the clearest wins of the custom element specification. Through the use of the static `observedAttributes` array and the associated `attributeChangedCallback` we are able to take fine-grained control over how our components react to changes declared directly in the markup describing them. That means the following code allows our newly defined custom element to react to changes in the value of the `custom-attribute` attribute and store that value as a local property.

```js
    class DeclarativeElement extends HTMLElement {
        static observedAttributes = ['custom-attribute'];
        attributeChangedCallback(name, oldValue, newValue) {
            switch (name) {
                case 'custom-attribute':
                    this.customProperty = newValue;
                    break;
            }
        }
    }
```

[Others](https://dev.to/richharris/why-i-don-t-use-web-components-2cia#6-confusion-between-props-and-attributes) have previously pointed out that managing _ALL_ of your attributes and their relationship to properties in this manner can be quite tiresome, and I would agree. Not having to manually wire everything you want to track in the HTML of your custom element to related properties one at a time is a great reason to work with libraries and tooling when developing web components. Luckily, we're already committed to using `LitElement` as a base class which helps us setup this relationship via its `static get properties()` API. Let's take a look at how the above is achieved therein:

```js
    class DeclarativeElement extends LitElement {
        static properties = {
            customProperty: {
                type: String,
                attribute: 'custom-attribute'
            }
        }
    }
```

Notice the change from `HTMLElement` to `LitElement` for our class extension. That change gives us access to a static properties getter that will outline the attributes we want to hear about changes to, and we receive an [extended list of options](https://lit-element.polymer-project.org/guide/properties) with which you can outline the relationship between the attributes and their associated properties. For our `<portal-entrace/>` element, we can outline a more declarative API, like so:

```js
    class PortalEntrance extends LitElement {
        static properties = {
            destination: {
                type: String
            },
            manual: {
                type: Boolean
            },
            open: {
                type: Boolean,
                reflect: true
            },
            order: {
                type: Number
            },
        }
    }
```

Adding a property in this way to a `LitElement` based custom element also means that changes to these properties will automatically kick off the update lifecycle of the component. In the case that these properties are used in building the DOM representation of your element, this is super helpful. However, being that none of these properties need to trigger a new render there are a couple of paths to optimizing reactive management of these attributes. We could extend these definitions to include `hasChanged() { return false; }` and prevent that entirely. Or, we could separately use the [`shouldUpdate`](https://lit-element.polymer-project.org/guide/lifecycle#shouldupdate) lifecycle method to prevent that holistically across the component. Further, knowing that there is zero processing that goes into understanding our element's template of `<slot @slotchange=${this.shouldProjectSlot}></slot>`, we can rely on [`lit-html`](https://lit-html.polymer-project.org/), the renderer underlying `LitElement`, to efficiently discover that there are no DOM changes to be made after any of those changes and not worry about extended configuration at all. So many options towards ensuring a more performant application! To ensure that our `<portal-entrance/>` elements are rendered once and then not worried about again, we'll pair the `shouldUpdate` and the `firstUpdated` lifecycle methods like so:

```js
    shouldRender() {
        return !this._hasRendered;
    }
    firstUpdated() {
        this._hasRendered = true;
    }
```

Here, our first update occurs unimpeded but by setting `this.shouldRender() = false` as part of that first update, no further updates to the rendered shadow DOM are made.

Right about now you might be asking, "If they don't trigger a render, what do these properties _even_ do?", and with good reason! First, let's remember that all of the DOM related to our portal is supplied as light DOM, and we use the `<slot/>` element in our template to listen to changes in that content for sending across the portal, which means internally we only need to render once, as shown above. When changes in the light DOM content occur, a call to `shouldProjectSlot()` will be made, which is where our component decides what to do with the DOM provided:

```js
    shouldProjectSlot() {
        if (!this.open) {
            if (!this.manual) {
                this.open = true;
            }
        } else if (this.manual) {
            this.projectSlot();
        }
    }
```

The most important thing to take away from this transaction is that when `manual === true` and `open === true` the `projectSlot()` method will be called directly allowing content placed into `<portal-entrance/>` to be streamed across the portal. Otherwise, when `manual === false`, `open` is set to `true`, which relies on the following getter/setter pair:

```js
    get open() {
        return this._open;
    }
    set open(open) {
        if (this.open === open) return;
        this._open = open;
        if (open) {
            this.setAttribute('open','');
            this.projectSlot();
        } else {
            this.removeAttribute('open');
            this.close();
        }
    }
```

Within this setter we eventually make that call to `projectSlot()` in this context as well, we just make a short detour to maintain a representative state on the way there. This allows us to worry about the fewest number of entries into the projection functionality as possible while also aligning the internal API of the `<portal-entrace/>` element with that available from the outside.

We'll match this with declarative updates to the API of our `<portal-destintion/>` element as well. These additions will leave our static properties getter looking like the following:

```js
    class PortalDestination extends LitElement {
        static properties = {
            name: {
                type: String
            },
            projected: {
                type: Array
            },
            multiple: {
                type: Boolean
            },
            announces: {
                type: Boolean
            },
            projecting: {
                type: Boolean
            }
        }
    }
```

Much of these additions will be discussed in greater depth along with the features they add below, but, before we move on, notice the `projecting` property. We'll use this in conjunction with the `projecting` attribute as a hook for styling this component when content is being projected into it. This being purely representational of internal state, it will be helpful to prevent this from being changed from the outside. While techniques like the use of underscore prefixed or `new Symbol()` based property names can support this sort of security, we can also manage this reality by only offering a setter for this value:

```js
    set projecting(projecting) {
        projecting = this.projected.length > 0;
        if (projecting) {
            this.setAttribute('projecting','');
        } else {
            this.removeAttribute('projecting');
        }
    }
```

Here we receive an incoming value and simply throw it away. At this time, I don't see needing this property for anything other than the styling hook, so we don't even need to cache it internally. In the `updated()` lifecycle method we'll use `this.projecting = 'update';` to initiate this functionality, and the setter will manage the presence of the `projecting` attribute.

With our declarative API prepared, controlling the `open` state and `destination` of a `<portal-entrance/>` becomes very straight forward. See it in action below:

{% glitch butter-perfume app %}

# Multiple Entrances

Now that we are more practiced on delivering the API for our portal in a declarative manner, doing so for additional features will hopefully become less and less daunting. One piece of functionality that we've previously discussed supporting and that can benefit from a declarative API is the ability to project content from more than one `<portal-entrance />` into a single [`<portal-destination/>`](https://dev.to/westbrook/your-content-in-shadow-dom-portals-3cdb#multiple-entrances); another feature originally outlined by the [Portal Vue](https://portal-vue.linusb.org/api/portal-target.html#multiple) project. We can power this with the addition of a `multiple` attribute to our `<portal-destination/>` element, as well as an `order` attribute to our `<portal-entrance/>` element. Usage might appear like this following:

```html
    <portal-entrance 
        destination="mutliple"
        order="1"
    >
        <h1>Second</h1>
    </portal-entrance>
    <portal-entrance 
        destination="mutliple"
        order="0"
    >
        <h1>First</h1>
    </portal-entrance>
    <portal-destination
        multiple
        name="mutliple"
    ></portal-destination>
```

In the above example, both of the `<h1/>` elements will be sent to the `<portal-destination/>` and due to the presence of `multiple`, both will be displayed therein. However, because of the values in the `order` attributes for those `<portal-entrance/>` elements, the first `<h1/>` will be displayed second, and the second `<h1/>` will be displayed first. To make this possible, we've added the `order` attribute to the static properties getter in our "entrance" element:

```js
    order: {
        type: Number
    }
```

With that attribute surfaced at the API level, it will then be available for delivering to our "destination" element via the `portal-open`:

```js
    projectSlot() {
        let content = this.portalContent;
        if (!content.length) return;
        this.dispatchEvent(createEvent('portal-open', {
            destination: this.destination,
            content: content,
            entrance: this,
            order: this.order || 0,
        }));
    }
```

On the "destination" side, there will be a good bit more that needs to change to support this addition. Before we get into those, we'll need to add the new attribute to its properties getter:

```js
    multiple: {
        type: Boolean
    }
```

Once again, this allows us to receive changes to this attribute via the `attributeChangedCallback` that `LitElement` connects directly to a matching property. With that available in our custom element, we'll then be able to use it to make decisions on how to respond to the various events that are being listened for. Specifically, we'll change the `updatePortalContent` method from being a catch-all for the most recently opened/closed `<portal-entrance/>` element to a gate for managing content differently depending on the value of `multiple`:

```js
    updatePortalContent(e) {
        this.multiple
            ? this.portalContentFromMultiple(e)
            : this.portalContentFromOne(e);
    }
```

That simple, right? Riiight.

To support both of these code paths, we'll create an intermediary map to cache the available content before flattening it into an array of arrays for pushing into our template. This means we'll create a `new Map()` that will be keyed by the actual `<portal-entrance/>` elements from which the content is delivered. The values will be structured as an object with both the received content, as well as the order value from the "entrance" element:

```js
{
    portal-element =>
        {
            content: node[],
            order: number,
        }
}
```

We'll build this data in response to the `portal-open` event via the following method:

```js
    cacheByOriginOnOpen(e) {
        if (e.type !== 'portal-open') return;
        this.projectedByOrigin.set(
            e.detail.entrance,
            {
                content: e.detail.content,
                order: e.detail.order,
            }
        );
    }
```

We'll use this map in the `multiple === false` path of our `updatePortalContent` functionality to decide whether the "destination" is currently receiving content from an "entrance" and to close that entrance before applying new content to the destination:

```js
    portalContentFromOne(e) {
        if (this.projectedByOrigin.size) {
            this.projectedByOrigin.keys().next().value.open = false;
        }
        this.cacheByOriginOnOpen(e);
        this.projected = e.detail.content || [];
    }
```

And, on the `multiple === true` path, the map will power our ability to sort the content by the `order` attribute delivered from the "entrance" and flatten the map into our expected `projected` property:

```js
    portalContentFromMultiple(e) {
        this.cacheByOriginOnOpen(e);
        const batchProjected = Array.from(
            this.projectedByOrigin.values()
        );
        batchProjected = batchProjected
            .sort((a,b) => a.order - b.order)
            .reduce((acc, projection) => {
                acc.push(projection.content);
                return acc;
            }, []);
        this.projected = batchProjected;
    }
```

When `portal-close` is dispatched, we'll use this structure to ensure only the content in question is being returned to the closing `<portal-entrance/>` element while also removing that element from the local cache before updating the portal content once again:

```js
    closePortal = (e) => {
        if (!this.confirmDestination(e)) return;
        this.returnProjectedWhenManual(e);
        this.projectedByOrigin.delete(e.detail.entrance);
        this.updatePortalContent(e);
    }
    returnProjectedWhenManual({detail: {manual, entrance}}) {
        if (!manual) return;
        const projected = this.projectedByOrigin.get(entrance);
        if (!projected) return;
        projected.content.map(el => entrance.appendChild(el));
    }    
```

In an actual application, this could exhibit a list of items for multiple selected with the `<portal-destination/>` playing the role of confirmation UI, allowing it to be located anywhere on the page. In the following example, the "selected" list will appear directly next to the ten options. However, in the DOM, the two lists are in completely different branches:

{% glitch wakeful-piano app %}

# Mirrored Listening

Up to this point we've relied on our `<portal-destination/>` elements being live and named when our `<portal-entrance/>` elements come knocking with their `portal-open` events. Paired with our recent addition of the `manual` attribute outlined above, this seems like a fairly complete API relationship between the two elements. However, what if our "entrance" is ready to _open_ before out "destination" is ready to _receive_? Whether through general runtime realities or as applied consciously when taking full control of your application's load process, it is feasible that you will run into a context where you intend for a `<portal-destination/>` to be lying in wait when you `open` a `<portal-entrace/>` and it's just not there. To support this, let's add some functionality to "announce" the presence or a change of name in our "destination" element. It's a great addition to the declarative API of our elements, we can do so, while also making it opt-in, by adding an `announces` attribute to our `<portal-destination/>` element. While we're at it, let's also make the `name` attribute reflect so that any changes we make to that value imperatively will be represented in the rendered DOM.

```js
    name: {
        type: String,
        reflect: true,
    },
    announces: {
        type: Boolean,
    }
```

With `LitElement` we have a couple of options as to where we'd like to react to changes in our properties. In this case, we can get all of the flexibility that we'll need by relying on the [`updated`](https://lit-element.polymer-project.org/guide/lifecycle#updated) lifecycle method. There we will receive a map keyed by values that have changed pointing to the previous value of those properties. This will allow us to test for changes to either `announces` or `name` with `changes.has()`, like so:

```js
    updated(changes) {
        if (changes.has('announces')) {
            this.shouldAnnounce();
        } else if (
            changes.has('name') &&
            typeof changes.get('name') !== 'undefined'
        ) {
            this.announce();
        }
        this.projecting = 'update';
    }
```

In the case of changes to `name`, when the value is being changed (not when being set initially from `undefined`) we'll immediately make a call to `announce()` the presence of the `<portal-destination/>` element. When it is the value of `announces` that has changed we'll make a call to `shouldAnnounce()` which confirms `announces === true` before calling `announce()`. This path is also added to the `connectedCallback` so that when the element is rejoining the DOM it will also announce itself when configured to do so. 

```js
    announce() {
        this.dispatchEvent(createEvent('portal-destination', {
            name: this.name,
        }));
    }
```

As you can see, the `announce` method is powered again by Custom Events, this time the `portal-destination` event. On the `<portal-entrance/>` side we'll listen for that event, using a listener attached to the `document` and the `capture` phase of that event so that it can respond accordingly with as little interference as possible:

```js
    connectedCallback() {
        super.connectedCallback();
        document.addEventListener(
            'portal-destination', 
            this.destinationAvailable,
            true
        );
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        document.removeEventListener(
            'portal-destination',
            this.destinationAvailable,
            true
        );
        this.open = false;
    }
    destinationAvailable = (e) => {
        if (e.detail.name === this.destination) {
            this.shouldProjectSlot();
        }
    }
```

And now we're listening on both sides of the portal. Our already thorough API is even more complete and we've further expanded the ways we can leverage our component manages content and the way it can display throughout our application. While it's not always easy to anticipate how realities of the loading process will affect the performance of our applications, in the following demo I've artificially delayed the `customElements.define()` call for the `<portal-destination/>` element so that you can experience what this enables. Run the demo with the console open to follow along on the delayed timing:

{% glitch mewing-character app %}

# Even More Styles

With the support for style application that we added as part of our cross-browser coverage, we now have a lot of control over how we style the content that we're sending over the portal. Styles contained within child components of our `<portal-entrance/>`s forwarded to our `<portal-destination/>`. `<style/>` tag children of those "entrances" are also forwarded to their assigned "destination", assuming that when ShadyCSS is required those elements are added after the `<portal-entrance/>`'s parent element's shadow DOM was initially polyfilled. However, when working with custom elements and shadow DOM we are offered an even wider array of possibilities to style our DOM.

There are some newer ways like working with [Constructible Stylesheets](https://dev.to/westbrook/why-would-anyone-use-constructible-stylesheets-anyways-19ng), and the number of immediate performance benefits they bring. In concert with the `adoptedStyleSheet` API, they also open an [expanded set of possibilities](https://benfarrell.com/2019/06/03/adopt-a-design-system-inside-your-web-components-with-constructable-stylesheets/) when working within predefined style systems. There are also more common concepts that need to be addressed like CSS Custom Properties.

The way that they offer a style bridge into the shadow DOM of a custom element is really powerful. However, when physically moving DOM from one part of the DOM tree to another it can take that content out of the cascade which those custom properties rely on to be applied appropriately. With those custom properties being difficult to acquire without previous knowledge of their presence, it is tricky to find productive/performant ways to move those properties along with the content that is being sent across the portal. These concepts and more being ripe for research, a follow-up article specifically covering style acquisition and application seems appropriate, even before this one is even done. 

# But, now what?

Beyond simply porting our `<portal-entrance/>` and `<portal-destination/>` elements to extending the `LitElement` base class, we've already done so much:

- prepared the elements for delivery cross-browser
- surfaced a declarative API
- added support to display content from `multiple` "entrances" in a single "destination"
- created a bi-directional relationship between the two elements so that the portal can open regardless of which is ready first

But, there is still so much to do!

Even before getting into the experimental work around supporting a more rich style application ecosystem, the most important next step is the addition of testing. Even just developing the demos for this article I found a number of edge cases that will need to be fully covered to call these components "production ready". I've done my best to fill in the holes as I wrote, but I'm sure there are things that I've missed and updates not appropriately reflected in this article. Focusing on the integration point between these two elements, there is much to be done in order to ensure future additions and refactoring do not affect the functionality we've worked on so far negatively. To that end, I will be spending some quality time with [Testing Workflow for Web Components](https://dev.to/open-wc/testing-workflow-for-web-components-g73) before getting back to you all with even more explorations on the other side of the portal. Try not to close the "entrance" while I'm gone.