I built a simple Web Component via Angular using Pascal Precht's tutorial, which you can see working HERE. It auto-magically compiles in the on Stackblitz in the link, but not locally.
My end goal is to have the code for the resulting Web Component in a separate file locally. Eventually, I will upload it somewhere and pull it in via a single <script>
tag, just like normal raw-html/javascript Web Components. I think the question speaks for itself, but you can read the details below if you would like:
To summarize my code in the link above, I have a very basic component:
import { Component } from '@angular/core';
@Component({
selector: 'hello-world',
template: `<h1>Hello world</h1>`
})
export class HelloComponent {}
and I have a module:
import { NgModule, Injector } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements'
import { HelloComponent } from './hello.component';
@NgModule({
imports: [BrowserModule],
declarations: [HelloComponent],
entryComponents: [HelloComponent]
})
export class AppModule {
constructor(private injector: Injector) {}
ngDoBootstrap() {
const HelloElement = createCustomElement(HelloComponent, {
injector: this.injector
});
customElements.define('hello-world', HelloElement);
}
}
Here is an explanation of the module above:
entryComponents
array so it's not taken out by the angular tree-shaker (since it's not reachable on app boot:
entryComponents: [HelloComponent]
Run my component through the createCustomElement
function so that I can use it as a regular html Web Component
:
const HelloElement = createCustomElement(HelloComponent, { injector: this.injector });
Finally, I ask Angular to compile this component in main.ts
:
platformBrowserDynamic().bootstrapModule(AppModule);
Here is the stuff I read / watched fully (among dozens of other links - most of which are dated, like the original Angular Elements intro):
Web Components from Scratch by Tomek Sułkowski (He never compiles it separately)
Web Components with CLI (Same problem)
Web Components by Academind (Yet again, this guy also uses them within Angular apps)
Thank you for any help.
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.
Angular Custom Elements are a recent addition to the Angular framework that allows you to create exportable components. In other words, whenever we create an Angular Element, we create a new custom HTML element that can be used on any webpage, even if that webpage does not use Angular at all.
import { NgModule} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HelloComponent } from './hello.component';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent, HelloComponent],
entryComponents: [HelloComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
make sure you use
npm install --save @angular/elements
and add "@webcomponents/custom-elements" : "^1.0.8"
in package.json.
After that run npm install
&
along with that you need to un-comment below lines from polyfills.ts
This adds a polyfill which is required for custom elements to work.
import '@webcomponents/custom-elements/custom-elements.min';
import '@webcomponents/custom-elements/src/native-shim';
<my-tag message="This is rendered dynamically">stack Overflow</my-tag>
Angular doesn't compile this above code, but angular elements fixes this issue by allowing to take our angular component and put it into totally encapsulated self bootstrapping HTML element which you can dump into your angular app in this following way for e.g and which will still work.
In AppComponent.ts file
import { Component, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements'
import { DomSanitizer } from '@angular/platform-browser';
import { HelloComponent } from './hello.component';
@Component({
selector: 'app-root',
template: '<div [innerHtml]="title"></div>',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = null;
constructor(injector: Injector, domsanitizer: DomSanitizer){
const customElement = createCustomElement(HelloComponent, {injector:
injector});
//this feature is not provided by angular it is provided by javascript
//this allows us to register custom web component
customElements.define('my-tag', customElement);
//instead of 'hello-world' i've used 'my-tag'
setTimeout(() => {
//security mechanism is used to avoid cross site attacks
this.title = domsanitizer.bypassSecurityTrustHtml('<my-tag message="This
is rendered dynamically">stack Overflow</my-tag>');
}, 1000);
}
}
And inside your HelloComponent
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'hello-world',
template: `<div> hello component -- {{ message }}</div>`,
styles: [`
div {
border: 1px solid black;
background-color: red;
padding: 1%;
}
`]
})
export class HelloComponent implements OnInit {
@Input() message : string;
constructor() { }
ngOnInit() {
}
}
Now this is loaded as native web component.Still only usable in angular projects, but already usable for dyanamic content like this.
I hope this will help you to run your code locally
Current Angular version doesn’t provide an option to export component as single local file which can used in any non angular application. However it can be achieved by making changes in building and deployment steps. In my example I have created two angular elements a button and alert message. Both components are compiled and exported as single local file which I’m loading in a plain html file with javascript.
Here are the steps follows: 1. Add ButtonComponent and AlertComponent in entryComponent list. In ngDoBootstrap and define them as custom elements. This is how my app.module looks:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AppComponent } from './app.component';
import { ButtonComponent } from './button/button.component';
import { AlertComponent } from './alert/alert.component';
@NgModule({
declarations: [AppComponent, ButtonComponent, AlertComponent],
imports: [BrowserModule],
entryComponents: [ButtonComponent, AlertComponent]
})
export class AppModule {
constructor(private injector: Injector) {
}
ngDoBootstrap() {
const customButton = createCustomElement(ButtonComponent, { injector: this.injector });
customElements.define('my-button', customButton);
const alertElement = createCustomElement(AlertComponent, { injector: this.injector});
customElements.define('my-alert', alertElement);
}
}
import {
Input,
Component,
ViewEncapsulation,
EventEmitter,
Output
} from '@angular/core';
@Component({
selector: 'custom-button',
template: `<button (click)="handleClick()">{{label}}</button>`,
styles: [
`
button {
border: solid 3px;
padding: 8px 10px;
background: #bada55;
font-size: 20px;
}
`
],
encapsulation: ViewEncapsulation.Native
})
export class ButtonComponent {
@Input() label = 'default label';
@Output() action = new EventEmitter<number>();
private clicksCt = 0;
handleClick() {
this.clicksCt++;
this.action.emit(this.clicksCt);
}
}
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'alert-message',
template: '<div>Alert Message: {{message}}</div>',
styles: [
`
div {
border: 1px solid #885800;
background-color: #ffcd3f;
padding: 10px;
color: red;
margin:10px;
font-family: Arial;
}
`]
})
export class AlertComponent {
@Input () message: string;
}
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.css"],
"scripts": [
{
"input":
"node_modules/document-register-element/build/document-register-element.js"
}
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "angular6-elements:build"
},
"configurations": {
"production": {
"browserTarget": "angular6-elements:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular6-elements:build"
}
}
runtime, polyfills, script
js files into single script file and export elements.js
which contains the custom elements
(optional: gzip those files)
serve it using http-server deploy --gzip"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod --output-hashing=none",
"package": "npm run package-base && npm run package-elements",
"package-base": "cat dist/{runtime,polyfills,scripts}.js | gzip > deploy/script.js.gz",
"package-elements": "cat dist/main.js | gzip > deploy/elements.js.gz",
"serve": "http-server deploy --gzip",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
}
script.js
and elements.js
in index.html (in deploy directory) to tell the browser about the custom elements.
Now my-button and my-alert can be included in index.html
. In this example, the button is shown on-load and Alert message is added dynamically (with counter number) on click of the button.
Here is the code: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Button Test Page</title>
<script src="script.js"></script>
<script src="elements.js"></script>
</head>
<body>
<my-button label="Show Alert Message!"></my-button>
<p></p>
<div id="message-container"></div>
<script>
const button = document.querySelector('my-button');
const msgContainer = document.querySelector('#message-container');
button.addEventListener('action', (event) => {
console.log(`"action" emitted: ${event.detail}`);
button.setAttribute("label", "Show Next Alert Message!");
msgContainer.innerHTML += `<my-alert message="Here is a message #${event.detail} created dynamically using ng elements!!!"></my-alert>`;
});
</script>
</body>
</html>
Here is my link for my git repo
Hope this will help!
Thanks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With