Cómo usar Web Components en Angular

Jorge del Casar
5 min readAug 13, 2018

--

🇺🇸🇬🇧 How to use Web Components with Angular

Photo by Alexandre Debiève on Unsplash

Ya os hablé de Los Web Components y los Frameworks y ahora toca ponerlo en práctica para que veáis que no solo funciona en la teoría. Como veis, según Custom Elements Everywhere, Angular pasa todos los test así que es un buen candidato para poner en práctica el uso de Web Components.

Todo lo desarrollado durante este artículo lo podréis seguir paso a paso en el repositorio jorgecasar/tutorial-webcomponents-angular.

Partiremos de una aplicación nueva, para lo que podéis usar el comando ng new tutorial-webcomponents-angular y la abrimos en nuestro editor favorito.

Añadir Custom Elements Schema

Lo primero, habilitamos los Web Components en nuestro proyecto incluyendo CUSTOM_ELEMENTS_SCHEMA en src/app/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { AppComponent } from './app.component';@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]

})
export class AppModule { }

Añadir Polyfills

Para asegurarnos la compatibilidad con navegadores antiguos es necesario incluir los polyfills de webcomponents.

Lo primero, instalar la dependencia usando NPM:

npm install --save @webcomponents/webcomponentsjs

A día de hoy no podemos incluirlo como módulo en el polyfills.ts por lo que tenemos que hacer un proceso más manual. Debemos indicar a Angular que debe copiar ciertos ficheros como assets en el fichero angular.json:

{
"glob": "{*loader.js,bundles/*.js}",
"input": "node_modules/@webcomponents/webcomponentsjs/",
"output": "node_modules/@webcomponents/webcomponentsjs"
}

Lo siguiente es añadir en el index.html la carga del script.

<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>

Y por último debemos esperar a que carguen las dependencias para iniciar nuestra app y así asegurarnos que los Web Components están listos para usarse:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
declare global {
interface Window {
WebComponents: {
ready: boolean;
};
}
}
if (environment.production) {
enableProdMode();
}
function bootstrapModule() {
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
}
if (window.WebComponents.ready) {
// Web Components are ready
bootstrapModule();
} else {
// Wait for polyfills to load
window.addEventListener('WebComponentsReady', bootstrapModule);
}

Soporte a ES5

Las clases de Custom Elements de ES5 no funcionarán en navegadores con Custom Elements nativos porque las clases de ES5 no pueden extender correctamente las clases ES6. Por ello si vas a servir tu app transpilada a ES5 necesitarás añadir este fragmento de código en el <head>, justo antes del script de webcomponents incluido antes.

<!-- This div is needed when targeting ES5.
It will add the adapter to browsers that support customElements, which require ES6 classes -->
<div id="ce-es5-shim">
<script type="text/javascript">
if (!window.customElements) {
var ceShimContainer = document.querySelector('#ce-es5-shim');
ceShimContainer.parentElement.removeChild(ceShimContainer);
}
</script>
<script type="text/javascript" src="node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
</div>

Con esto ya tenemos lista nuestra app para incluir Web Components, así que vamos a crear uno y comprobar su compatibilidad.

Crear un Web Component

Vamos a instalar lit-element, una librería utltra ligera para la creación de Web Components de Justin Fagnani:

npm i --save @polymer/lit-element

Creamos un componente simple llamado wc-mood:

import {LitElement, html} from '@polymer/lit-element';class WebComponentsMood extends LitElement {
static get properties() {
return { mood: String }
}
_render({mood}) {
return html`
<style>
.mood { color: #1976d2; }
</style>
<h1>Web Components are <span class="mood">${mood}</span>!</h1>`;
}
}
customElements.define('wc-mood', WebComponentsMood);

Y por último lo importamos en el fichero typescript de nuestro componente, en este caso app.component.ts:

import './wc-mood/wc-mood';

Y lo usamos en el html de nuestro componente:

<my-element mood=”awesome”></my-element>

Poniendo a prueba la interacción con el Web Component

Ahora que tenemos el Web Component funcionando vamos a probar la interacción con él.

Establecer propiedades desde Angular

La primera prueba es comprobar que el componente reacciona cuando se establece una propiedad desde Angular. Para ello, creamos la propiedad mood y un método randomMood que cambia dicha propiedad:

export class AppComponent {
moods: Array<string> = ['awesome', 'formidable', 'great', 'terrifying', 'wonderful', 'astonishing', 'breathtaking'];
mood: string;
constructor() {
this.randomMood();
}
randomMood() {
const index = Math.floor(Math.random()*this.moods.length);
this.mood = this.moods[index];
}
}

Y hacemos el correspondiente cambio en el html para establecer la propiedad y hacemos que al pulsar el logo de Angular establezca otro valor a la propiedad:

<wc-mood [attr.mood]="mood"></wc-mood>
<img (click)="randomMood()"/>

Escuchar eventos desde Angular

Para completar la interacción vamos a lanzar un evento desde el componente para escucharlo desde Angular.

En el Web Component vamos a notificar los cambios en las propiedades enviando el evento:

_didRender(_props, _changedProps, _prevProps) {
this._notifyPropsChanges(_props, _changedProps);
}
_notifyPropsChanges(_props, _changedProps) {
for(let prop in _props) {
this.dispatchEvent(
new CustomEvent(prop + '-changed', {
detail: { value: _changedProps[prop] }
})
);
}
}

Por simplificar, notificaremos todos los cambios en las propiedades. Y para estandarizar enviaremos el evento [prop]-changed donde [prop] es el nombre de la propiedad, en nuestro caso mood. Esto lo hacemos porque es lo más lógico desde mi puto de vista y además tanto Angular como Polymer utilizan este patrón, por lo que podemos empezar a estandarizarlo 😜

Una vez que hemos realizado este cambio ya podemos escuchar el evento desde Angular. Para comprobar que angular recibe el evento vamos a animar el logo de Angular durante 1 segundo mediante el siguiente método:

isChanged: boolean = false;moodChanged() {
this.isChanged = true;
setTimeout(() => this.isChanged = false, 1000);
}

Y añadimos lo vinculamos en el html, escuchamos el evento y asignamos la clase animated a la imagen :

<wc-mood [mood]="mood" (mood-changed)="moodChanged()"></wc-mood>
<img (click)="randomMood()" [class.animated]="isChanged" />

Doble data binding

Si queréis rizar el rizo y simplificar la forma de establecer y escuchar el evento podemos utilizar el doble data binding:

<wc-mood [(mood)]="mood"></wc-mood>

Angular en este caso escuchará el evento mood-changed y asignará el valor a la propiedad. De esta forma podemos llamar al metodo moodChanged cuando el valor cambie:

private _mood: string;public get mood():string {
return this._mood;
}
public set mood(value:string) {
if(this._mood !== value) {
this._mood = value;
this.moodChanged();
}
}

Demo

Para que veáis el funcionamiento aquí os dejo la demo:

--

--

Jorge del Casar

Web technologies & Assistant #GDE • Development Technologies #MVP • #WebComponents