Implementar Angular Elements

Ignacio Buioli
- 21/07/2018

La versión 6 de Angular trajo novedades muy importantes para el desarrollo que día a día realizamos sobre el software. Una de las herramientas que más auge está teniendo en Angular Elements: un potente sistema que convierte nuestros componentes (components) de Angular en WebComponents, lo que nos permite utilizarlos en cualquier implementación JavaScript, con o sin otros frameworks. A continuación dejamos un pequeño instructivo que desarrollamos en Moldeo Interactive para poder compilar un componente con Angular Elements. Al final de la entrada les dejamos un video, completamente en español, que realizamos para darle soporte audiovisual a dicho instructivo.

Prerequisitos: Angular CLI 6 (recordemos que a partir de Angular 6 los números de versiones con el CLI se emparejaron), con lo cual es altamente recomendable hacer npm i -g @angular/cli para asegurarnos de tener la última versión estable. Si vamos a usar Elements en un proyecto viejo de Angular, necesitaremos actualizar el Angular CLI local y los paquetes con el comando ng update.

 A partir de aquí podemos comenzar con un proyecto nuevo creado con ng new o implementar Angular Elements, en cualquier caso necesitaremos ejecutar el siguiente comando en nuestro proyecto:

 

ng add @angular/elements

 

Este comando es una conjunción de un npm install con una copia de archivos de Angular. Lo que va a hacer es instalarnos el paquete @angular/elements como dependencia y además agregar los archivos para los polyfills (en caso de que queramos una mayor compatibilidad). Vamos a trabajar sobre nuestro app.component aunque podríamos utilizar cualquier otro componente. Lo primero es importar los objetos nuestro app.module.ts:

 

import { Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';

 

Aquí traemos a Injector del @angular/core y a la función createCustomElement de @angular/elements, la cual se encargará de crear nuestro WebComponent. A continuación necesitaremos configurar el @NgModule para indicarle que nuestro componente forma parte del array entryComponents, así es como debería quedar:

 

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [],
  entryComponents: [AppComponent]
})

 

Finalmente, vamos a modificar la función de export de la clase AppModule, para indicarle que necesita compilarlo en un WebComponent:

 

export class AppModule {
  constructor(private injector: Injector) {
    const el = createCustomElement(AppComponent, { injector });
    customElements.define('my-button', el);
  }
  ngDoBootstrap() {}
}

 

Hay que prestar atención a lo que ocurre en estas pocas líneas. La función createCustomElement convierte nuestro componente de Angular en un WebComponent, sin embargo -como puede verse en el bloque de arriba- este debe guardarse en una constante para ser usado posteriormente. Luego de esto, se llama a la función define de customElements la cual lleva dos parámetros: un selector y un WebComponent (el que acabamos de crear). El selector será el nombre de la tag que llevará nuestro WebComponent, y lleva siempre un guión que separe dos palabras para así distinguirse de las tags de HTML5.

Del app.module ya no vamos a necesitar modificar nada más (como puede verse es bastante rápida la implementación de Angular Elements). Necesitamos simplemente encapsular nuestro componente de Angular, para eso nos dirigimos al app.component.ts e importamos lo siguiente:

 

import { ViewEncapsulation } from '@angular/core';

 

Lo vamos a agregar al decorador, el cual nos tiene que quedar de la siguiente manera:

 

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  encapsulation: ViewEncapsulation.Native
})

 

Esto va a encapsular todo lo que contenga nuestro componente (HTML, CSS y JS) en un solo archivo de JavaScript, necesario para utilizar el sistema más simple de implementación en nuestro HTML donde irá nuestro WebComponent. Todo lo que incluyan en su app.component.html y app.component.css (y por supuesto su archivo TS) será lo que componga su WebComponent (y por supuesto pueden utilizar variables tipo Input o Output para crear atributos y listener en su WebComponent, pero eso es material para otra entrada).

Antes de compilar, vamos a realizar un cambio en nuestro archivo tsconfig.json, modificando el target: es5 por target: es2015. Esto se debe a que Angular Elements funciona con es2015 y no con es5 (da un error al probar el JavaScript generado). Esperemos que lo mejoren en futuras versiones.

Y si, aún hace falta un paso más antes de ejecutar nuestro build y es concatenar todos los archivos de JavaScript que va a generar el compilado en un solo archivo de JavaScript, a fin de hacerlo más práctico para su utilización. Lamentablemente, Angular aún no incluye una flag que lo haga automáticamente, pero siempre podemos hacer un simple script que nos lo haga. Para eso vamos a instalar las siguientes dependencias:

 

npm i --save-dev fs-extra concat

 

Viejas conocidas de NodeJS. Con esto, en nuestra carpeta root, vamos a crear un archivo llamado concatenate.js (puede tener cualquier nombre, siempre que sea .js, pero se suele utilizar ese) y su contenido será el siguiente:

 

const fs = require('fs-extra');
const concat = require('concat');
(async function build() {
const files = [
'./dist/<NOMBRE-DEL-PROYECTO>/runtime.js',
'./dist/<NOMBRE-DEL-PROYECTO>/polyfills.js',
'./dist/<NOMBRE-DEL-PROYECTO>/scripts.js',
'./dist/<NOMBRE-DEL-PROYECTO>/main.js',
]
await fs.ensureDir('elements')
await concat(files, 'elements/my-element.js');
//await fs.copyFile('./dist/<NOMBRE-DEL-PROYECTO>/styles.css', 'elements/styles.css')
//await fs.copy('./dist/<NOMBRE-DEL-PROYECTO>/assets/', 'elements/assets/' )
})()

 

Donde dice <NOMBRE-DEL-PROYECTO> debemos reemplazarlo por el nombre de nuestro proyecto de Angular (el nombre de la carpeta root, frecuentemente). Esto va a generar un archivo my-element.js en una carpeta nueva llamada elements. Las últimas dos líneas que están comentadas hay que utilizarlas solo si se utilizan styles globales (no recomendable) o assets (tampoco recomendable) respectivamente.

Ahora sí, llegó el momento de compilar, para eso usaremos la siguiente línea de comando en la carpeta root:

 

ng build --prod --output-hashing none && node concatenate.js

 

Esto va a producir, al menos, un archivo llamado my-elements.js en una carpeta llamada elements (si la carpeta no existe procederá a crearla). ¡Ya tenemos listo el WebComponent! Solo nos queda probarlo, creando un simple archivo index.html en la carpeta elements con el siguiente contenido:

 

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Angular Elements</title>
</head>
<body>
    <my-button></my-button>
    <script type="text/javascript" src="my-element.js"></script>
</body>
</html>

 

Al abrirlo en un navegador compatible, tenemos que ver nuestro componente de Angular funcionar en un entorno externo (como una simple página web). Recordatorio: actualmente solo soportan WebComponents las últimas versiones de Chrome, Opera y Safari. Firefox lo soporta mediante el uso de una flag de forma nativa y tanto Edge como IE11 no lo soportan. Para dar soporte a estos últimos, se ofrecen unos polyfills.

 

Recuerden consular el Video para soporte Audiovisual.