AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

There are many articles and talks about Angular Elements. They say that it is easy to compile Web Components and use them on the page without Angular application.

But people consider a rather utopian situation: we make a separate project, create an Angular component, configure the project to build Angular Elements, and finally compile several JS files that we can use on a usual web page. Yay, the component works!..

In practice, you have a working Angular project and want to compile its components to Web Components. And do it in a way that does not affect current development and use of your Angular project.

I wrote this article due to a similar situation. I had to compile the Angular UI components library into a set of native Web Components.

Prepare your modules

A typical module for compiling Angular Elements looks like this:

Typical module to compile Web Component

So, we need to:

Add an Angular component that we want to build as Web Component to entryComponents and import modules that it needs Create Angular element using createCustomElement and Injector Do customElements.define to register Web Component in a browser Override ngDoBootstrap to do nothing

The first step is the declaration of the component and its dependencies. Other steps are needed in order to show our Web Component in a browser. We can separate the logic for the latter into an abstract superclass.

The superclass gets a component, its name and injector to build the whole Web Component and register it. The module to create a specific element looks like this:

In this example, we import a usual Angular module with a button and declare the component in entryComponents. We also get an injector from DI scope.

The module is ready for compilation. As a result of it, we get a set of JS files that we can use as a Web Component. Now we can create several similar modules and compile them to Web Components.

Compiling many components

Now we need to bootstrap our modules. I like the idea of separating the logic into a standalone executable file that will be responsible for compiling a particular module.

The project structure looks like this:

And the elementary version of compile.ts file:

This way we have a simple and clear project structure.

We can override the output path in angular.json with a temporary folder inside dist:

"outputPath": "projects/elements/dist/tmp"

And we can use usual ng build command from angular-cli:

ng run elements:build:production --main='projects/elements/src/${project}/${component}/compile.ts'

Each element is a final result of our project that is why we need to add a production flag with Ahead-of-Time compilation.

Now we can concat output files that we need in a Web Component into the final bundle. Using cat:

cat dist/tmp/runtime.js dist/tmp/main.js > dist/tmp/my-${component}.js

It is important to note that we do not concat polyfills.js file into each bundle because we get duplication of them using several components on one page. And do not forget to disable outputHashing option in angular.json.

We need to copy this bundle to final folder where we will have all Web Components after compilation.

cp dist/tmp/my-${component}.js dist/components/

And the final compilation script:

So we have a folder with a set of Web Components:

How to use it on a web page

Our Web Components can be used independently. We can insert them when we need.

<my-elements-input id="input">Input</<my-elements-input> <script src="my-input.js"></script>

Angular uses zone.js. We can insert it once.

<script src="zone.min.js"></script>

Cool! The input component is on the page. Let’s add a button.

<my-elements-button size="l" onclick="onClick()">

Button

</my-elements-button> <script src="my-button.js"></script>

And we have an error:

Something went wrong.

In each bundle we can find the following line:

window.webpackJsonp=window.webpackJsonp||[]

Webpack patches window object to prevent duplicating of modules. Only the first added component can add itself to customElements.

We need to use custom-webpack to solve the problem:

Add custom-webpack to the project with Angular elements:

ng add @angular-builders/custom-webpack --project=elements

2. Configure angular.json:

3. Create a configuration for custom-webpack:

Now all our components can be on one page without any problems.

Add styles

We use CSS variables in our components library to set color themes and sizes of components. Using Angular we have a root element of Angular application that contains our styles. We do not have such an option using Web Components, but we can compile our styles and insert them into the page:

We use less-css and that is why we compile our variables with lessc and put the result into the ‘helpers’ folder.

This way we can manage styles of our app for all Web Components without recompilation.

Final script

We can reduce the whole compilation process to a small script:

#!/bin/sh rm -r -f dist/ &&

mkdir -p dist/components &&

node compileElements.js &&

node compileHelpers.js &&

rm -r -f dist/tmp

Calling the script from our package.json, we compile all our components in the current version with one command.

You can find all the code from this article on Github.

Finally

Now we can add new Web Components in a couple of minutes with the structure of the original Angular project.

Every developer can add a new component in a set without problems and use compiled JS bundles in his or her apps without any knowledge about how Angular Elements work.