Implementar Angular Elements
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.
Acerca de:
Ignacio Buioli
Degree on Multimedia Arts. He has developed numerous Multimedia projects as well as written articles and translated texts of the mentioned subject. In Moldeo Interactive, He is a Partner and Programmer; also taking care of a large part of the online networks and courses.