Angular Component Portals

Anthony Miller
ITNEXT
Published in
4 min readAug 22, 2020

--

There has been much talk and hype around Angular Ivy, with it being hailed as the saviour for many things that will come to Angular ecosystem of the future, this includes to be able to lazily load an individual component at runtime using the import syntax without the need of a NgModule itself.

this.foo = await import(`./foo.component`)
.then(({ FooComponent }) => FooComponent);

Netanel Basal wrote an excellent post on this topic covering all the in’s and out’s on lazy loading individual components and how to use them (see link below).

Why do we want this at all? What’s all the fuss?

If you take the dashboard of a typical administration application. There are many differing aspects mostly displaying of analytical data to the user and each one of these panels can be components from various modules, but how to load these on demand or even how can the panel be customised by the end user themselves and dynamically loaded.

Loading a dashboard via the Angular routing isn’t possible for these types of scenarios, as the dashboard would be on a single route notable /home or /dashboard.

But what if you could lazily load a NgModule and instantiate any component inside the module without a direct reference to the component type?

Introducing Component Portals

Component Portals provides the ability to instantiate any component by using a string identifier and the component is magically materialised. Well maybe not magically but you get my drift.

This pattern can be easily sprinkled into any existing application with little effort, well almost.

Component Portals was inspired in part by Wes Grimes (see link below) and it has taken me some to get this published for use as public npm package and best of all it doesn’t even require Angular Ivy, the current ViewEngine works perfectly fine.

Let’s get started

Firstly we need to install the component portal library that does all the heavy lifting of component register. Install the library using the following command:

npm install @codethatstack/portals

Once installed, import the CtsPortalsModule into applications root NgModule:

Registering Components

Create and register any component as you normally would with a NgModule:

Next step is register the component with ComponentRegistry:

This step is important, this is the magical glue that binds the Component Type and NgModule to the string component identifier. This only registers the concrete component type, now we need to stitch this together in the root NgModule.

Registering Portal Modules

Define a module identifier (moduleId) for each NgModule that will be lazy loaded. This uses same syntax as lazy loading child routes.

Next register the PortalModules using the injection token PORTAL_MODULE_TOKEN.

Registering Portal Components

Define each component using their string identifier’s as defined above linking it to the module identifier above.

By separating out the module loading definition this avoids duplication when registering many components for the same NgModule.

Next is to register the PortalComponents using the injection token PORTAL_COMPONENTS_TOKEN.

Wow that was a lot of plumbing work.

Ready, Set, Go

To instantiate a component anywhere inside your application use the directive ctsComponentPortal, specifying the component’s string identifier name.

That’s it! The module will be loaded and the component instantiated inside the current ViewContainer.

If needed you can even hook into the activated and deactivated events to handle any additional logic.

Bonus

I was recently introduced to the concept of Teleporting by Nir Kaufman. The concept is very straight forward. Register an outlet within your application and then attach a template to that outlet, however with Component Portals it will be a component instead.

Register the Portal Outlet somewhere inside your application provide a unique name.

Then use the property ctsComponentPortalAttachTo to specify which outlet the component will be attached to.

In summary

By sprinkling a bit of magic a component can be instantiated anywhere using just a string identifier.

Check out the demo here.

Additional Resources

--

--