Angular Elements: A Guide to Shipping Framework-Agnostic Libraries

Shane Williamson
Vendasta
Published in
5 min readAug 21, 2019
Angular Elements logo (source)

Angular elements is an exciting piece of technology from the Angular team. The Angular team describes elements as [1]:

Angular elements are Angular components packaged as custom elements (also called Web Components), a web standard for defining new HTML elements in a framework-agnostic way.

This means that we are able to create Angular components and use them anywhere, be it alongside Vanilla JS and HTML, or with any framework such as Angular, React, Vue, etc.

I have a few objectives for this blog:

  • To share knowledge about how to distribute awesome components/libraries/packages in a framework agnostic way. This includes shipping your code to NPM while following Angular package standards, as well as a framework-agnostic bundle able to be used anywhere;
  • To describe some of the problems and solutions that Angular elements can solve;
  • To cover a few of the gotchas that my team and I encountered along our journey.

My team and I recently built a library that works within our Angular applications, and also outside of them. For the library to be usable in these multiple different projects, we needed to build it in such a way that it is agnostic of the differences between the projects it would be used in. Angular elements were the perfect tool for that scenario.

Overview

1. Project File Structure
2. Build Configurations
3. Building the Libs
4. Gotchas

Project File Structure

The road to easily distributing your libraries starts with the project’s file structure. After several iterations my team has come to a structure that works great. Of course you’re free to do whatever works for you, but this is what we found to be quite effective, especially when shipping multiple libraries from within the same repo. The structure is as follows:

Example File Hierarchy

Explanation

The first key piece of the structure here is the angular.json file at the top-level of the project. This defines our workspace configuration[2], including build configurations we’ll set up for each library defined within our projects directory. Also, note the presence of the typical package.json and tsconfig.json files here.

At the top level, there are two directories, projects, and src. The projects directory contains all the libraries we will be distributing from this repository. The src directory contains the testing application for testing our libraries.

Each library within the projects directory will have the same general structure. Within a library we have our general high-level files such as our CHANGELOG and a package.json file. Within the library’s src directory we have lib and elements directories. The lib directory is the location where almost all of the libraries’ code will be. The elements directory will contain code responsible for using and building the library code with Angular elements.

Build Configurations

There are two types of build configurations to be set up for our end goal to be met. One build will be for our NPM library output which will adhere to Angular Package Format (APF)[3] and be convenient for usage within Angular. The other will be an Angular elements build which we’ll be able to use anywhere.

Here is an example, stripped-down, workspace configuration with two build configurations which we will use to build our library for both NPM distribution and as an Angular element:

angular.json with example build projects

Let’s first take a look at the build configuration for building an NPM library. This is denoted under the projects section as library1. To achieve the desired Angular Package Format we will need to declare this as a library:

”projectType”: “library”

and use ng-packagr[4]:

”builder”: “@angular-devkit/build-ng-packagr:build”

That’s all we need to do to set up the NPM build!

The second build configuration listed as library1-elements will be for that of our Angular elements build. Note the difference in the projectType and builder configurations.

We need to build our Angular elements components as an application and use the default builder seen in other normal Angular applications.

It should also be noted that each build configuration has its own tsconfig file, with options specific to each.

tsconfig.lib.json for NPM build target
tsconfig.elements.json for Angular elements build target

Differences between builds

I suggest creating two different App Modules, one for the NPM version of the library, and the other for the elements version. This way you only include Angular elements-specific libraries, polyfills, and web-element definitions when you need them.

Building the Libraries

Now that we’ve described how we will build our libs we need to actually build them. Using ng-packagr our NPM build is basically done as is aside from actually publishing it to NPM. To get our Angular elements build output to what we want will take a bit more work.

After building our Angular elements version:

ng build --prod library1-elements

We will now have several build artifacts to work with:

runtime.js, polyfills.js, main.js

The three files listed above are what makes our library. Another optional step is to put them all together for easy distribution by concatenating them using a build script:

Script for concatenating Angular build artifacts

Now that we’ve successfully built our single JS file we can go ahead and distribute our framework-agnostic library through a CDN.

And we’re done!

Gotchas

Although most of my team’s journey with Angular elements was pain-free, we did occasionally run into some issues.

Build Target

During the build process for Angular elements you may see that the custom web elements libraries have issues with the target being >es2015. This can be solved either by changing the target to es2015 or by using an adapter that supports a further version.

Other solutions can also be found online to this end [5].

Script Import

When importing and using the Angular element, you may find that it doesn’t work unless you have your script tag after your Angular element in the DOM. A simple fix is to simply reorder the tags.

Element Inputs Casing

When passing in values to an Angular element you need to use kebab-case for any input names on the DOM element.

Eg. A component with a string input named myCoolInput:

<my-component my-cool-input=”foo” …></my-component>

--

--