Raúl Jiménez

elecash@gmail.com

@elecash

Angular

Elements

about me

Angular GDE

Videogular

Consultancy

Byte

Default

Web

Components

Definition

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.

Specifications

  • Custom Elements
    Define new DOM elements
  • HTML Templates and Slots
    Define markup fragments
  • Shadow DOM
    Styles encapsulation
  • ES Modules
    Modular and reusable JS documents

Define an element

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!

Define a Component

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()

Lifecycle

Similar to Angular and other frameworks too

  • attributeChangedCallback()
    Called when an attribute changed
  • connectedCallback()
    Called each time the element is added to the markup
  • disconnectedCallback()
    Called each time is removed from the markup

Angular Lifecycle

It looks quite similar to Web Components

  • ngOnInit()
    Called once when the @Component() is created
  • ngOnChanges()
    Called when an @Input() changed
  • ngOnDestroy()
    Called when the @Component() is removed

Templates

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

Angular Templates

<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

Custom Elements

Even the Custom Elements API is very familiar

Properties

@Input()

Events

@Output()

Attributes

@HostBinding()

Methods

Public functions

And so on...

Because Angular is actually very close to the spec!

  • Custom Elements
    Define new DOM elements
  • HTML Templates (and now Slots too)
    Define markup fragments
  • Shadow DOM
    Styles encapsulation
  • ES Modules
    Modular and reusable JS documents

And so on...

Because Angular is actually very close to the spec!

  • Angular @Component()
    Define new Angular components
  • Angular Templates and content projection
    Define markup fragments
  • ViewEncapsulation
    Styles encapsulation
  • NG Modules
    Modular and reusable Angular Modules

So if we're so close...

Why not to compile Angular Components

as Web Components?

Angular

Elements

Definition

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

Angular Elements...

In Angular v7!

but before we start...

<ngx-toggle>

A Toggle component, because we're tired of the classic checkbox, right?

@NgModule

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 {
}

@Component

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...
}

@Component

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...
}

@Component

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

Slots

Definition

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

Using <slot>

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

Slots in Angular

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"

Slots in Angular

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

Demo

Compiling

Compiling

Enable Ivy Renderer

{
    "extends": "../tsconfig.json",
    "compilerOptions": {
        "outDir": "../out-tsc/app",
        "types": []
    },
    "angularCompilerOptions": {
        "enableIvy": true
    },
    "exclude": [
        "test.ts",
        "**/*.spec.ts"
    ]
}

Compiling

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...
    }
}

Resources

Web Components

Angular Elements