Teleporting Content In Angular

Breaking Free From The Top Down Model

Lucio Francisco
Jan 19 · 3 min read
Courtesy of Angular

Angular is a brilliantly designed framework and we’re (usually) glad of structuring our web application by its rules.

Every Angular’s application is like a tree, so, that the parent template contains the children components and every child displays its content within its view only. And that’s usually fine.

However, what if we could enable children components to break through the limits of their own views, so, for them to display some of their widgets on a parent toolbar or pieces of information on a global status bar without data binding restrictions of any sort?

That’s what I call “teleporting” content.

Our Use Case

To demonstrate this idea, let’s imagine the following use case:

Using wm-portal component

The app component, at the root level, implements a simple navigation bar with common links from one side and a toolbar on the other side.

The toolbar is not implemented by the app component itself since we want every page to have its own custom toolbar. It makes use of a wm-portal component, instead, where the toolbar content will materialize.

Each page, then, implements its own toolbar to be teleported:

Using wmTeleport directive

In this example, our page has a counter that can be incremented, decremented and cleared by the toolbar.

The toolbar content is defined within a ng-template using the wmTeleport directive to target the ‘toolbar’ portal, so, whenever this page will be activated the content of the toolbar will be teleported back to the parent app.

However, the toolbar template will still remain part of this page for data inputs and event to be bound to and that’s what make this technique really powerful.

The Teleport Service

The content transfer is possible thanks to a TeleportService connecting the directives to the portals:

The Teleport Service

The service is implemented as an injectable BehaviorSubject streaming TeleportInstance(s) where the TemplateRef to transfer is paired with a key telling which portal we want the template transferred to.

The activate() method pushes a new instance along while the clearAll() method pushes null for all the portals to clear-up.

The Portal Component

Every portal has a unique name to be addressed with:

The Portal Component

Here we make use of the @Attribute() decorator to get the name of the portal assuming this will remain static once defined.

The component builds an observable out of the TeleportService filtering those instances targetting this very portal and mapping the instance to its contained TemplateRef.

The incoming template is then rendered making use of the ngTemplateOutlet structural directive wrapped in a ng-container where the template$ observable is resolved using the async pipe (see line #3).

As the name suggests, the context input accepts an object to be provided as the template context, so, for the portal to optionally supply the template with destination-specific variables.

Note that ngTemplateOutlet directive accepts null templates as valid, simply clearing up the view in such cases.

The Teleport Directive

The last piece of our teleporting mechanism is a directive collecting the content to transfer:

The Teleporting Directive

The directive is designed to work in conjunction with ng-template pseudo-elements only.

The portal name to target comes from its primary input, so, potentially changing during the execution. That’s why we make use of the ngOnChanges() life-cycle hook to monitor the input changes clearing-up the previous target (if any) and pushing the template to the new target portal for rendering.

Last, but not least, we clear-up the target portal within thengOnDestroy() life-cycle hook, so, for the teleported content to disappear together with its source container.

Conclusions

And this is how we created a pattern letting you define content somewhere to be rendered somewhere else. This really sounds like teleporting to me.

There are many advantages to this approach:

  1. Widgets are defined within the very components they need to interact with.
  2. The rendering can take place everywhere in the app, no matter the hierarchy.
  3. Angular takes care of everything for us from change detection to view update despite we are kind-of cheating its component/view model.

Try it Yourself

The code described here is part of wizdm.io, an open-source project hosted on Github. Feel free to reach me out at hello@wizdm.io for whatever question or curiosity.

The code is also available in a fully functional live demo on StackBlitz you can play with:

Live demo on StackBlitz

Wizdm Genesys

An attempt in combining modern software development with ancient wisdom

Lucio Francisco

Written by

I believe that whatever problem we’re puzzling ourselves with, once we really get to the bottom of it the solution has to be simple

Wizdm Genesys

An attempt in combining modern software development with ancient wisdom

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade