Create external webcomponents with Angular Elements

Create external webcomponents with Angular Elements

With Angular Elements you are able to create components, bundle them into one file and use them in other projects. The normal procedure to create a component is to use the CLI, generate a component. This component is then strongly wired within the project. The approach to create a webcomponent is the same, the only difference is, that we will use a build script which creates a single javascript file. This javascript bundle file can be loaded into any html: <script src="source/of/the/script">. Sounds great! and the implementaion is very easy.

Step by step tutorial

We will create an external component from scratch. If you don't have setup angular or npm I highly recommend to read our getting started page first.

Initialize a new Angular project

Let's use the CLI to initialize a new project called widget.

ng new widget

Install dependencies

We need some dependencies to create external webcomponents. Angular Elements and webcomponents are not preinstalled so we have to add them manually. We need two other packages for our build script. The first package fs-extra gives us the ability to read and save files to the hard drive. The second package concat helps us to concat/merge our different js files into one single.

npm i @angular/elements @webcomponents/custom-elements fs-extra concat --save

We need to add two scripts to our angular.json into scripts section of build. This two scripts will be packed inside the bundle and are mandatory.

"scripts":
[
  "node_modules/@webcomponents/custom-elements/custom-elements.min.js",
  "node_modules/@webcomponents/custom-elements/src/native-shim.js"
]

Setup component

You can create a new component or you can use the existing app.component.ts file if it's a simple component. In this tutorial we will use the app.component.ts file for simplicity. We will remove the selector from @Component because we will define a custom name for it later. For styling purposes of your component, use the components applied .css file app.component.css. It will be also packed into the bundle.

Component logic

For the sake of simplicity we will create one @Input() decorator which is binded in the html. If you want to create a more complex component please read Generic components with Angular 6 CLI. See the code below:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @Input() title;
}

This is the app.component.html file with a simple h1 tag.

<h1>{{title}}</h1>

Setup component registration

Inside AppModule.ts we have to create our custom element and define a name for it. Follow these steps to achieve this.

  1. remove AppComponent from bootstrap.
  2. add entryComponents[] to the module.
  3. add AppComponent in entryComponents[] array.
  4. add a constructor with injected Injector from @angular/core.
  5. create AppModules ngDoBootstrap() function.
  6. create a custom element.
  7. define the name of the custom element
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [],
  entryComponents: [AppComponent]
})
export class AppModule {
  constructor(private injector: Injector) {  }

  ngDoBootstrap() {
    const widgetName = createCustomElement(AppComponent, {injector: this.injector});
    customElements.define('widget-name', widgetName);
  }
}

Build script

The component needs to be packed in one js bundle file. At the moment the Angular CLI doesn't have a build script built-in so we have to create our own. Create a build-elements.js file in root (where your package.json and angular.json is located). Change the widget-name variable initialization to a name which your component describes best.

// build-elements.js
const fs = require('fs-extra');
const concat = require('concat');
const widgetName = 'widget-name';

(async function build() {
  const files = [
    './dist/widget/scripts.js',
    './dist/widget/runtime.js',
    './dist/widget/main.js',
  ];
  await fs.ensureDir('elements');
  await concat(files, `elements/${widgetName}.js`);
})().catch(err => console.debug(err));

Call build script

add this line to your package.json under scripts.

"build:elements": "ng build --prod --output-hashing none && node build-elements.js"

Now you can run the script with this command npm run build:elements. A directory elements will be created and you should get inside this directory a module-name.js file.

Use the custom webcomponent

To load a webcomponent in another project you can add it into any html file or load it for example by clicking a button. Either way you have to do two steps:

Option 1

<html>
<head>
  <script src="path/widget-name.js">
</head>
<body>
  <widget-name title="This is awesome"></widget-name>
</body>
</html>

Option 2

addWidget() 
{
   const script = document.createElement('script');
   script.type = 'text/javascript';
   script.src = 'assets/widget-name.js';
   document.body.appendChild(script);
   const widget = document.createElement('widget-name');

    // The widgets element is a simple <div> with id="widgets"
   const widgets = document.getElementById('widgets');
   if (!widgets) {return;}
   widgets.appendChild(widget);
 }

Thats it! Now you can create simple or complex components and load them in any web project.

Feel free to leave your feedback or suggestions. You can find me at Twitter or simply send me an email to tomislav.eric@arconsis.com.