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
From what I read the packaging specific for Angular Elements components for easy use outside Angular will come with Angular 7.
What you can do now is to create and angular application with the cli.
ng new YourAppName
Add the Angular Elements library with:
ng add @angular/elements
This adds also all required polyfills as described in the official Angular Documentation.
Then you change the AppModule to not be a bootstrap module but just register the custom elements. You remove the bootstrap from the NgModule and ad the components as entry components. Then register the components as custom elements in the ngDoBootstrap
hook. I made both the default AppComponent and HelloComponent custom elements. This is how my app module looks like:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AppComponent } from './app.component';
import { HelloComponent } from '../hello/hello.component';
@NgModule({
declarations: [
AppComponent,
HelloComponent
],
imports: [
BrowserModule
],
providers: [],
entryComponents: [AppComponent, HelloComponent]
})
export class AppModule {
constructor(private injector: Injector) {
}
ngDoBootstrap() {
customElements.define('app-root', createCustomElement(AppComponent, {injector: this.injector}));
customElements.define('hello-world', createCustomElement(HelloComponent, {injector: this.injector}));
}
}
Then you can use the elements in the index.html like elements for example like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ElementsTest</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<div>
<app-root></app-root>
</div>
<div>
<hello-world></hello-world>
</div>
</body>
</html>
If you build this with ng build --prod
you get minimized packages that you could use also now in other html pages by including the package scripts as they get included by the compiler in the index.html file.
I have added my sample to GitHub. In the history you can see what I have changed from the initial cli application.
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.
Hello there.
If i am understanding correctly, you want to generate a Web Component (lets say <my-component></my-component
) and then with a simple script tag to get the .js file to initialize that component and add it on any html page you want to.
In my GitHub repository i have created a simple Todo List Component. That component follows the Angular Elements Principles and also, i have installed some file managment libraries for webpack to also pack the JS into one JS file.
You can check this repository out and see if that helps you out. Just clone it and then run npm install followed by npm run build:elements Feel free to contact me if anything goes south.
Also check this guide. This guy has helped me a lot.
Best of luck
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