Wanna create Angular Elements? Here’s how!

The “from-scratch” guide to authoring Web Components with Angular (in 10 steps)

Recently there’s been a lot of talk about the possibility of creating Web Components from a regular Angular component, but so far there are much fewer practical examples as to how to build one–in the sense of actually making the bundle that you could just add to an html and have a working custom element. It should be very easy in the future but for now we need to make do with some manual tinkering.

I do recommend you to read/watch linked presentations to learn about the specific possibilities of the technology (spoiler: they are endless 😉) but in the meantime let’s build a component for ourselves, with a focus on a minimal tooling thet we should need:

This is a from-scratch approach. I will follow this article with a setup based on Angular CLI project in separate post later this week.
Update: The CLI-based article is ready!

1. Initialize the project:

mkdir angular-element-demo
cd angular-element-demo
npm init -y demo

2. Install main dependencies:

Add the minimal amount of regular Angular family members:
(we need the latest, not yet officially released version 6 for this… 
update: Angular 6 has been released, I’ve updated the article to reflect that)

npm i @angular/{core,compiler,common,platform-browser,platform-browser-dynamic}

… and friends:

npm i core-js rxjs zone.js

Now, for the elements we will need the Angular library and a polyfill:

npm i @angular/elements @webcomponents/custom-elements

3. Install dev dependencies

Let’s see: we will need Angular compiler, webpack, ngtools to combine them.. oh, and TypeScript:

npm i -D @angular/compiler-cli @ngtools/webpack webpack webpack-cli typescript@~2.7

Ok, enough installing for now. Now let’s write configurations.

4. Configure webpack

Again, as small configuration as possible–whatever is not necessary we skip, and we use whatever defaults webpack 4 provides:
(never heard of webpack? See its documentation for the details on the options set)

The minimal webpack.config.js that we need is:

module.exports = {
module: {
rules: [{test: /\.ts$/, loader: '@ngtools/webpack'}]
resolve: {
extensions: ['.ts', '.js']
plugins: [
new NgCompilerPlugin({
tsConfigPath: './tsconfig.json',
mainPath: './src/index.ts'
mode: 'production',
stats: 'errors-only' // there are… few deprecation warnings that
}; // I expect to go away soon (Angular 6.0?)
// for now, let's not see them ;)

5. Configure TypeScript

Our minimum viable tsconfig.json looks like this:

"compileOnSave": false,
"compilerOptions": {
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"target": "es2015",
"lib": ["es2017", "dom"]

That’s the end of our required setup! Wasn’t that bad, I hope?

Now let’s get to the code. Our bundle will be built from only 3 source files: 
the component definition, module definition and the bootstrap code.

6. Component definition

You could put really almost any component here, the important thing is to use ViewEncapsulation.Native so that the styles are bundled in one js file with the component’s template and logic. Other than that there is absolutely nothing special about it.

For our purpose we’ll use something simple that will also illustrate Angular’s Input and Output logic working within native web component.

The src/button.component.ts looks like this:

I’ve included a screenshot here for readability, but if you want to copy the code you can get it here: https://gist.github.com/sulco/f1e69d0e453bd9c753639454af2bc099

7. Module definition — the interesting part

So this is the one part that is the most crucial, and specific to Angular Elements, of this whole setup. We will use the Angular’s createCustomElement function to create a class that can be used with browsers’ native customElements.define functionality.

Angular documentation describes this best:

createCustomElement Builds a class that encapsulates the functionality of the provided component and uses the configuration information to provide more context to the class. Takes the component factory’s inputs and outputs to convert them to the proper custom element API and add hooks to input changes.
The configuration’s injector is the initial injector set on the class, and used by default for each created instance.This behavior can be overridden with the static property to affect all newly created instances, or as a constructor argument for one-off creations.

The src/button.module.ts file:

What is also special about this module is that, since our ButtonComponent is not a part of any other component, and is also not a root of an Angular application, we need to specifically tell Angular to compile it: for this we put it on the entryComponent list.

We also need to tell Angular to use this module for bootstrapping, hence the ngDoBootstrap method.

8. Bootstrap code

As for the bootstrap, nothing extremely complex going on here. Just your typical bootstraping code, with only the addition of custom-element shim.

The src/index.ts file:

9. Build

All pieces are now in place, we just need to blast off webpack. Let’s add a build script to package.json file:

"scripts": {
"build": "webpack"

and run npm run build.

Ok, now what? We should have a main.js file created in the dist/ folder, but does it even do anything? Let’s try to run it.

10. Running the code & some optimizations

Since we have an output and input defined we will put our custom element on the page, listen to it’s event, and change it’s attribute’s value in our sample code:

The [projectFolder]/index.html file will look like this:

To serve this page we will use a simple http-server npm module, so let’s install it: npm i -D http-server and add to npm scripts in package.json:

"scripts": {
"build": "webpack",
"serve": "http-server --gzip"

Now run npm run serve, visit with devtools opened and click the button to see the even’s handling.

Let’s take a moment to enjoy our (hopefully!) moment of triumf 🤓

Ok, you might have noticed that the dist bundle weights around 325kB. That’s heavy! In a not so distant future we will be able to get this down to much, much, smaller size thanks to Angular Ivy rendering engine that is planned for Angular 7 release, but for now we can at least minify and gzip what we have.

For this let’s install 2 last dev dependencies:

npm i -D uglifyjs-webpack-plugin compression-webpack-plugin

and use them within our webpack.config.js:

After running npm run build again, we should now have a bit (well, several bytes actually) smaller js file, but what’s more important also an additional gzipped file, that will be the one that we will serve to our users — and that one is a much lighter 85kB. It’s not perfect, but it is a substantial improvement.

Btw. you can browse the completed code on Github.


Phew! That was quite a ride! Note that we did it by hand and from scratch
It should, and will, be much easier in the future. We can expect everything to be configured within the Angular CLI, and creating an element build will be a matter of a single cli command.

But if you started hearing about Angular Elements and was eager to try them out — that’s one of possible solutions. As I mentioned, I will share the second one in a following article.

Did you learn something new? If so please:

→ clap 👏 button below️ so more people can see this
follow me on Twitter (@sulco) so you won’t miss future posts!

See the CLI-based approach here: