Raúl Jiménez
elecash@gmail.com
@elecash
Angular GDE
Videogular
Consultancy
Web components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps.
Source: webcomponents.org
class HelloWorld extends HTMLElement {
constructor() {
super();
}
sayHi(name) {
console.log(`hello ${name}`);
}
}
// Define new DOM element
window.customElements.define('hello-world', HelloWorld);
// Create DOM element instance
var helloWorld = document.createElement('hello-world');
// Add DOM element instance to the markup
document.body.appendChild(helloWorld);
// Use custom element
helloWorld.sayHi('Raúl');
You can try this code on the browser console!
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'hello-world',
templateUrl: './hello.component.html',
styleUrls: [ './hello.component.scss' ],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloWorld {
constructor() {
}
sayHi(name: string) {
console.log(`hello ${name}`);
}
}
@Component() decorator is similar to define()
Similar to Angular and other frameworks too
It looks quite similar to Web Components
HTML Templates (we'll see later <slot>)
<table id="producttable">
<thead>
<tr>
<td>UPC_Code</td>
<td>Product_Name</td>
</tr>
</thead>
<tbody>
<!-- Include #productrow dynamically -->
</tbody>
</table>
<template id="productrow">
<tr>
<td class="record"></td>
<td></td>
</tr>
</template>
Source: MDN web docs
<ngx-toggle [size]="45" [width]="100" [height]="50" (change)="onChange($event)">
<span slot="on" class="text">ON</span>
<span slot="off" class="text">OFF</span>
</ngx-toggle>
Child
<input #cb type="checkbox" id="switch" (click)="onClick($event)">
<label for="switch" [ngStyle]="styles.label">
<span class="dot" [ngStyle]="styles.dot"></span>
<span class="on" [ngStyle]="styles.on">
<slot name="on"></slot>
</span>
<span class="off" [ngStyle]="styles.off">
<slot name="off"></slot>
</span>
</label>
Parent
Even the Custom Elements API is very familiar
Properties
@Input()
Events
@Output()
Attributes
@HostBinding()
Methods
Public functions
Because Angular is actually very close to the spec!
Because Angular is actually very close to the spec!
Why not to compile Angular Components
as Web Components?
Angular elements are Angular components packaged as custom elements, a web standard for defining new HTML elements in a framework-agnostic way.
Source: angular.io
In Angular v7!
but before we start...
A Toggle component, because we're tired of the classic checkbox, right?
Define your entryComponents and schemas
import { BrowserModule } from '@angular/platform-browser';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ToggleComponent } from './toggle/toggle.component';
@NgModule({
declarations: [
AppComponent,
ToggleComponent
],
imports: [ BrowserModule ],
bootstrap: [ AppComponent ],
entryComponents: [ ToggleComponent ],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class AppModule {
}
Create your @Component() as you always did
// Imports...
@Component({
templateUrl: './toggle.component.html',
styleUrls: [ './toggle.component.scss' ],
encapsulation: ViewEncapsulation.ShadowDom,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToggleComponent implements OnInit, OnChanges {
@ViewChild('cb') cb;
@Input() size = 90;
@Input() width = 200;
@Input() height = 100;
@Output() change = new EventEmitter<boolean>();
constructor(private ref: ChangeDetectorRef) {}
// Component logic here...
}
Don't add any "selector" or you will have conflicts when you define the Web Component
// Imports...
@Component({
selector: 'ngx-toggle',
templateUrl: './toggle.component.html',
styleUrls: [ './toggle.component.scss' ],
encapsulation: ViewEncapsulation.ShadowDom,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToggleComponent implements OnInit, OnChanges {
// Component logic here...
}
Use ViewEncapsulation.ShadowDom which has been introduced in Angular v7
// Imports...
@Component({
templateUrl: './toggle.component.html',
styleUrls: [ './toggle.component.scss' ],
encapsulation: ViewEncapsulation.ShadowDom,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToggleComponent implements OnInit, OnChanges {
// Component logic here...
}
For the DOM this means using modern Shadow DOM and creating a ShadowRoot for Component's Host Element
The HTML <slot> element—part of the Web Components technology suite—is a placeholder inside a web component that you can fill with your own markup, which lets you create separate DOM trees and present them together.
Source: MDN web docs
The truth is that Slots are very cool!
<template id="element-details-template">
<span>
<code class="name"><slot name="title"></slot></code>
<p class="desc"><slot name="description"></slot></p>
</span>
<hr>
</template>
<element-details>
<span slot="title">HTML Templates</span>
<span slot="description">A mechanism for holding client-
side content that is not to be rendered when a page is
loaded but may subsequently be instantiated during
runtime using JavaScript.</span>
</element-details>
<script>
// Define Web Component
</script>
Source: MDN web docs
You can use Slots in Angular Elements!
In your @Component() template add
<slot name="name-id">
In your parent @Component define your Web Component with name="name-id"
Parent @Component
<ngx-toggle (change)="onChange($event)">
<span slot="on" class="fas fa-dollar-sign"></span>
<span slot="off" class="fas fa-euro-sign"></span>
</ngx-toggle>
<input #cb type="checkbox" id="switch" (click)="onClick($event)">
<label for="switch" [ngStyle]="styles.label">
<span class="dot" [ngStyle]="styles.dot"></span>
<span class="on" [ngStyle]="styles.on">
<slot name="on"></slot>
</span>
<span class="off" [ngStyle]="styles.off">
<slot name="off"></slot>
</span>
</label>
Angular Element
Enable Ivy Renderer
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []
},
"angularCompilerOptions": {
"enableIvy": true
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
Fix document-register-element dependency
{
// Other stuff here...
"devDependencies": {
"@angular-devkit/build-angular": "~0.10.0",
"@angular/cli": "~7.0.3",
"@angular/compiler-cli": "~7.0.0",
"@angular/language-service": "~7.0.0",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "~4.5.0",
"document-register-element": "1.8.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~3.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
// etc...
}
}