Angular Template Outlets as a Workaround for Component Placement Restrictions

Zak Barbuto
NextFaze
Published in
4 min readJul 25, 2019
The best code is like a desert — DRY… Look, it works, okay, give me some credit. Image credit: Keith Hardy on Unsplash.

TL;DR Check the StackBlitz at the bottom.

Some Angular libraries may have restrictions on component placement. Ionic is one of them. Certain Ionic components, such as ion-fab need to be direct descendants of ion-content to work. Despite several issues in the past (dating back to 2016) and claims that it was fixed in the latest version it still appears to be an issue at the time of writing (Ionic version 4.6). It’s clearly some sort of technical limitation in getting the fab to render fixed against the scrolling content.

Ionic is not the only library with these sorts of restrictions — I’ve experienced similar issues in the past with other Angular component libraries. So, Idecided to come up with a workaround.

The Problem

I’m sure there are several reasons for these sorts of restrictions in an Angular libraries — that is, requiring a specific DOM hierarchy for that component to work. However, there are also many reasons to want to wrap one of those third-party components in your own component. You might want some consistent features or styling for that component across pages. Maybe you want some consistent analytics on a button — or added functionality. Or maybe you want the same fab group — with the same 4 social media options (an example from the Ionic documentation) — across a dozen pages in your app:

It looks something like this when expanded:

Fab example from the Ionic docs

You don’t want to copy-paste that block of code across every single page. It makes your page templates brittle and doesn’t follow good DRY coding practices. In general, testable and re-usable components are a Good Thing™.

So what can we do? Are we stuck?

No. There is a workaround. Depending on your views on TemplateRef usage, you might view it as a bit of a “hack”. In my view, though, sometimes the hack is way better than the alternative.

First, let’s look at our page component. We use some nice re-usable components to keep everything tidy:

But, as we’ve established, the FAB won’t work properly. If it’s not the direct descendant of ion-content — which it’s not since it’s a child of my-reusable-fab— it won’t be properly fixed on the page (it will not scroll with the content). What we really want is for Angular to render the contents of my-reusable-fab in place without it actually being a child of my-reusable-fab.

A Solution

Note a solution — not the solution. What we can do is use <ng-template>s in conjunction with ngTemplateOutlet directive. If you’re not familiar with Angular structural directives and template usage it’s worth reading up on as it provides some of the most powerful functionality in Angular. The updated components are going to look something like this:

Okay — so there’s a few things going on here. In the fab component — we declare a template and assign it to the#templateContent variable. The setter in the component class reads in that variable as a ViewChild and the emits it to our templateOutput EventEmitter . The page component then basically passes those template emissions through to the template outlet. Because ng-template is not actually rendered in the DOM, the result is that the contents of our reusable fab is places as a direct descendant of our ion-content .

Voilà — we have a reusable fab.

Ok, so maybe it’s not the most elegant solution — but it’s a pattern that could save you a bit of bother if — four example — your client decides they want to add their Pinterest link to the fab group which is on every page in the app. It might be overkill if your app only has a couple of pages — but in a very large app this it is going to save you a bunch of time and heartache. It also means we only need to write unit tests for the FAB, and wire up any analytics or additional actions, once. Great.

Here’s a stackblitz demonstrating some actual working code if that’s your thing.

If you’ve got some alternate solutions feel free to drop a comment and I’ll add them to the list.

--

--