Skip to main content

Web Components

References

Set up a component as a web component

Create an Angular application

Note

Most of the tutorials suggest turning off routing and sticking with CSS but I used SCSS and it seemed to work fine.

Most of the tutorials suggest turning off routing and sticking with CSS but I used SCSS and it seemed to work fine.

ng new {your-app-name}

Add Angular Elements

cd your-app-name ng add @angular/elements

Running the add @angular/elements command will add two items to the application’s package.json dependency list: @angular/elements and document-register-element

Add a component to the application

ng generate component componentName

Note

If routing is turned off, add the component to src/app/app.component.html. This way, you will be able to test the component by running the app.

<app-component-name></app-component-name>

If routing is turned off, add the component to src/app/app.component.html. This way, you will be able to test the component by running the app.

<app-component-name></app-component-name>

Set component’s encapsulation

These are rules on how isolated the component will be from the outside environment.

  • Add ViewEncapsulation to the import statements

  • Add the encapsulation param to the @Component metadata.

    • Possible choices are: Emulated, Native, None, and ShadowDom
  • Comment out the selector param since we’ll be defining the element selector in the app module file.

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

@Component({
//selector: 'msg-wc',
templateUrl: './msg-wc.component.html',
styleUrls: \['./msg-wc.component.scss'\],
encapsulation: ViewEncapsulation.Emulated
})
export class MsgWcComponent {

constructor() { }

}

Make the component a custom element

To this point, the component is a normal component within the application. We need to specify that this component is a custom element.

Update app.module.ts

import  { Injector, CUSTOM\_ELEMENTS\_SCHEMA} from '@angular/core';
import { createCustomElement } from '@angular/elements';

Add the component to the bootstrap array

@NgModule({ 
declarations: \[
AppComponent,
MsgWcComponent
\],
imports: \[
BrowserModule
\],
providers: \[\],
schemas: \[ CUSTOM\_ELEMENTS\_SCHEMA \],
bootstrap: \[AppComponent , MsgWcComponent\]
})
export class AppModule {

Inject Injector as a dependency

export class AppModule {
constructor(private injector: Injector) {}
}

Transform the component to a custom element

export class AppModule {
constructor(private injector: Injector) {
const el = createCustomElement(MsgWcComponent, { injector });
customElements.define('msg-wc', el);
}

Add an ‘output’ directory and scripts to build to this directory (optional)

Add a new directory called ‘output’ to the application’s root. This directory can be used to concatenate all the generated JS files together and house a preview of the web component using a local NPX instance.

Add HTML for preview

The .js references are based on the generated JS file names outlined in the next step.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>App Preview</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css"></head>
<body>
<app-root></app-root>
<script src="app-es2015.js" type="module"></script>
<script src="app-es5.js" nomodule defer></script>
</body>
</html>

Create a script to copy files from ‘dist’ to ‘output’

Create a Js file in the application’s root (e.g. build-output.js)

const fs = require('fs-extra');
const path = require('path');
const concat = require('concat');
const appName = 'basic-wc';

var copyFile = (file, dir2)=>{
var f = path.basename(file);
var source = fs.createReadStream(file);
var dest = fs.createWriteStream(path.resolve(dir2, f));

source.pipe(dest);
source.on('end', function() { console.log('Succesfully copied'); });
source.on('error', function(err) { console.log(err); });
};

concatenate = async () =>{
const filesEs5 = \[
\`./dist/${appName}/runtime-es5.js\`,
\`./dist/${appName}/polyfills-es5.js\`,
\`./dist/${appName}/main-es5.js\`
\];

const filesEs2015 = \[
\`./dist/${appName}/runtime-es2015.js\`,
\`./dist/${appName}/polyfills-es2015.js\`,
\`./dist/${appName}/main-es2015.js\`
\];

await fs.ensureDir('./output');
await concat(filesEs5, './output/app-es5.js');
await concat(filesEs2015, './output/app-es2015.js');
}
concatenate();

copyFile(\`./dist/${appName}/favicon.ico\`, './output/');
copyFile(\`./dist/${appName}/styles.css\`, './output/');

Update package.json

Install some libraries that will be used to concatenate the JS files and host a preview of the component.

npm install --save-dev concat
npm install --save-dev fs-extra
npm install --save-dev npx

Add the following to the package.json scripts.

"package:output": "ng build --prod --output-hashing=none && node build-output.js",
"preview:output": "npx live-server output",

Consume an Angular Web Component

Sample setup to consume an Angular web component from another Angular application.

Update app.module.ts

Import CUSTOM_ELEMENTS_SCHEMA

Import CUSTOM\_ELEMENTS\_SCHEMA and add it to the @ngModule metadata’s schemas list.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM\_ELEMENTS\_SCHEMA } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import '../../bin/web-components/BasicWcEs2015';

@NgModule({
declarations: \[
AppComponent
\],
imports: \[
BrowserModule,
AppRoutingModule
\],
providers: \[\],
schemas: \[ CUSTOM\_ELEMENTS\_SCHEMA \],
bootstrap: \[AppComponent\]
})
export class AppModule { }

Import a reference to the web component JS

In this particular example, I created a bin/web-components directory at the root of the app that is consuming the web component.

File directory example

Since the app is set up to target es2015, I pointed to the es2015 version of the web component’s JS in app.module.ts.

import '../../bin/web-components/BasicWcEs2015';

Tell TypeScript to allow importing JS files

Add "allowJs": true to the compilerOptions configuration in tsconfig.json.