Crea tu primer componente con Angular Elements en 6 pasos

Publicado por Jesus Cuesta Arza el

FrontAngularAngular ElementsWeb Components

Los webcomponentes y/o componentes reutilizables son un requisito cada vez más importante en las grandes empresas. Si estás trabajando con Angular tienes diferentes posibilidades para su reutilización. Una de ellas es Angular Elements.

¿Qué es Angular Elements?

Cuando hablamos de Angular Elements estamos hablando de una funcionalidad que añadió Angular en la versión 6 de Angular CLI:

Angular elements are Angular components packaged as custom elements, a web standard for defining new HTML elements in a framework-agnostic way

Es decir, podremos crear un paquete con componentes o micro-proyectos de Angular como un custom element.

Pero no todos los navegadores son compatibles. Por ejemplo, mientras que con las versiones actuales de Chrome no tendríamos problemas, con Firefox deberíamos activar una opción que tiene. De todas formas, a lo largo del artículo os explicaremos cómo solventar parte de estos problemas para que vuestro componente se pueda visualizar en todos los navegadores.

En este sentido, desde la página de Angular, nos indican su compatibilidad con cada navegador:

  • Chrome - Supported natively.
  • Opera - Supported natively.
  • Safari - Supported natively.
  • Firefox - Supported natively as of version 63. In older versions: Set the dom.webcomponents.enabled and dom.webcomponents.customelements.enabled preferences to true.
  • Edge - Working on an implementation.

1.- Instalación

Ahora que ya sabemos un poco más qué es un Custom Element y sus limitaciones, si aún deseas usarlo con Angular, vamos a ir paso por paso para generar nuestro primer prototipo con Angular Elements:

Architecture Angular Elements

1.1- Instalación Angular

Para aquellos que no tengan Angular CLI instalado, o con una versión antigua, lo primero que vamos a hacer es asegurarnos que nuestras dependencias están instaladas y actualizadas:

Instalación o Update de Angular CLI

En el caso de que no tengáis instalado la librería de Angular CLI, procedemos a instalarla:

npm install -g @angular/cli  

Para aquellos que ya estuviesen trabajando con Angular, pero que no estén seguros de qué versión tienen, vamos a comprobar primero qué versión tenemos instalada. Abrimos una terminal y ejecutamos:

ng --version  

Os aparecerá una imagen parecida a esta:

Angular version

En el caso de que tengamos una versión anterior de Angular CLI a la 6, deberemos hacer un update:

ng --update  

2.- Creación proyecto

Una vez tenemos instalada una versión compatible de Angular CLI, procederemos a crear un proyecto para añadirle posteriormente la librería y crear nuestro componente.

Creamos un proyecto de ejemplo y le añadimos el prefijo que deseemos, aunque este parámetro sea opcional:

ng new cejs-elements --prefix atsistemas  

Si estamos trabajando con las últimas versiones de Angular CLI, puede que nos pregunte si queremos routing y algún precompilador. En esta versión básica, y sobre todo si no tenéis experiencia con Angular, mejor no selecciones el routing.

Luego nos metemos dentro de la carpeta:

cd cejs-elements  

Si miramos con el Visual Studio Code o con otro editor la estructura del proyecto, veremos que es un proyecto estándar de Angular.

3.- Instalando dependencias

A continuación añadimos la librería de Angular Elements al proyecto:

ng add @angular/elements  

Otro detalle interesante, que no tiene que ver con Angular Elements, pero que nos ayudará a que nuestro proyecto de Angular tenga mejor compatibilidad con los navegadores, es el archivo src/polyfills.js. En este caso, como queremos que el componente que vamos a generar tenga una compatibilidad alta, vamos a proceder a descomentar todas las dependencias de dicho fichero y a instalar estas dos, que se nos indica en el mismo:

npm install --save classlist.js  
npm install --save web-animations-js  

Posiblemente, en algunos casos no tendréis que hacer esto, pero de esta forma, abarcamos genéricamente más posibilidades.

Si en este momento ejecutásemos el comando para generar el componente independiente y luego lo intentásemos visualizar, nos daríamos cuenta que no no funciona en todos los navegadores. Para ello, vamos a instalar otra dependencia, que nos facilitará que se vea correctamente en otros navegadores, como por ejemplo Firefox:

npm install --save document-register-element@1.8.1  

4.- Configurando Angular Elements

Una vez que tenemos instalado y configurado adecuadamente nuestro proyecto Angular y hemos añadido las dependencias necesarias, vamos a proceder a crear nuestro primer componente reutilizable.

Para ello deberemos ir a app.module.ts, que es el fichero inicial donde podemos gestionar importaciones del proyecto (entre otras cosas) y vamos a añadirle en la parte superior con los otros import:

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

Estos dos elementos nos permitirán inyectar el componente que deseamos reutilizar dentro de la propiedad createCustomElement.

Luego, donde declaramos la clase de AppModule, le añadiremos el contenido donde definiremos el componente:

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

  ngDoBootstrap() {}
}

Las partes más importantes en este código, sería donde definimos AppComponent y donde indicamos el nombre del componente cejs-elements. Aquí estamos indicando el contenido que queremos externalizar y el nombre del custom element.

Un problema que podemos encontrarnos, es que al no indicar bien el nombre, cuando vayamos a reutilizarlo, nos indique que no lo encuentra.

Además, deberemos cambiar la propiedad bootstrap del módulo, por un entrycomponents. Esto es debido, a que dicha propiedad nos indica qué componente debe ejecutar al cargar la aplicación y lo sustituiremos por la propiedad, que tantas veces usamos para modales y otros componentes que deseamos reutilizar dentro de la aplicación en otros módulos:

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

Esto nos dejaría el fichero con este contenido:

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule, Injector } from '@angular/core';  
import { createCustomElement } from '@angular/elements';

import { AppComponent } from './app.component';

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

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

  ngDoBootstrap() {}
}

5.- Compilación

Existen diferentes formas de compilar el proyecto para generar el build del custom element. En este caso, en el que vamos a convertir toda la aplicación en un custom element, vamos a ver las diferentes opciones que tenemos.

Script para Concat

Primero, deberemos crear un fichero con un script, que luego lanzaremos para unificar todos los js y CSS en sólo un fichero cada uno.

Creamos un fichero con el nombre elements-build.js, en la ruta base, con este contenido:

const fs = require('fs-extra');  
const concat = require('concat');

(async function build() {
    const files = [
        './dist/cejs-elements/runtime.js',
        './dist/cejs-elements/polyfills.js',
        './dist/cejs-elements/scripts.js',
        './dist/cejs-elements/main.js'
    ];

    await fs.ensureDir('elements');
    await concat(files, 'elements/cejs-elements.js');
    await fs.copyFile(
        './dist/cejs-elements/styles.css',
        'elements/styles.css'
    );
})();

Podemos ver en el contenido que cogerá los archivos javascript indicados, y los unficará en uno solo. Y que luego realizará lo mismo con los CSS. Además, los añadirá en una carpeta a parte llamada elements, para que los podamos usar fácilmente.

Como necesitamos dos dependencias para ejecutar el script, las instalaremos en las devDependencies del proyecto:

npm install fs-extra --save-dev  
npm install concat --save-dev  

Build

Para poder crear el build, deberemos ir a package.json y crear un nuevo script:

"scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e",
        "build:elements": "ng build --prod --output-hashing none && node elements-build.js"
    },

Hemos añadido el build:elements,  que nos ejecutará un build sin el sistema de hasting. Esto lo hacemos, para seleccionar fácilmente los js en el anterior script y luego ejecutará el comando de node sobre ellos:

Lanzamos el script, desde la terminal:

npm run build:elements  

En el caso de que al hacer el build os saliese un error con Terser:

ERROR in runtime.82c6613acef8f7246fe8.js from Terser  

Por lo visto hay un problema con webpack, a fecha de hoy, lo podéis solucionar añadiendo dicha librería como una dependencia con esa versión específica:

npm install terser@3.14.1 --save  

6.- Testear el componente

Si revisamos las carpetas de nuestro proyecto, podremos ver como en la carpeta dist, se ha creado el compilado normal del proyecto. Pero la que nos interesa, es la carpeta elements. En esta carpeta, está nuestro custom element.

Dentro de la carpeta nos encontraremos dos ficheros, uno con todo el javascript unificado y el otro con el CSS unificado.

Para poderlo testear, necesitaremos crear un index.html y añadir nuestro custom element. Lo más rápido, es ir a dicha carpeta y crearlo. En dicho archivo, añadiremos este contenido:

<!doctype html>  
<html lang="es">

<head>  
    <meta charset="utf-8">
    <title>Angular Elements</title>
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>  
    <cejs-elements></cejs-elements>
    <script type="text/javascript" src="cejs-elements.js"></script>
</body>

</html>  

Ahora, para cargar nuestra aplicación con nuestro custom element, nos bastaría con levantar un servidor que ejecutase dicho html.

En este caso, lo vamos a hacer con static server. Instalamos esta dependencia, en el caso de que no la tengamos:

npm -g install static-server  

Una vez instalada, nos vamos a la carpeta donde tenemos la web sencilla y levantamos el servidor:

cd elements  
static-server  

Esta librería nos levantará un servidor, que podremos ver en el navegador en la siguiente URL: http://localhost:9080/.

Otros detalles

Una de las diferencias que tiene solamente un custom element, con un web component es el shadow DOM. En algunos tutoriales para utilizar Angular Elements, nos recomiendan que indiquemos a Angular que se use Shadow DOM. Esto se haría, añadiendo dicha propiedad al componente:

encapsulation: ViewEncapsulation.Native  

Pero para mejorar la compatibilidad con navegadores, hemos decidido eliminarla en este ejemplo.

Repositorio

Todo el proyecto generado para este artículo lo podéis encontrar en el github de atSistemas, en artículos: https://github.com/atSistemas/articles-cejs/tree/master/cejs-elements

Conclusión

En estos momentos, para reutilizar componentes con Angular, podríamos optar por el típico copia pega (poco aconsejable), por el uso de web components por nuestra cuenta, el uso de library de Angular, el uso de Stencyl o el uso de Angular Elements.

La ventaja de la opción de este artículo, radica en que podríamos utilizar dichos componentes o microproyectos con otras tecnologías y/o proyectos, sin tener que depender de Angular o una versión específica de Angular.

Pero hay que tener en cuenta, que un componente casi vacío, nos ocupa 345 KB. Lo cual nos lleva a la duda de cuánto ocuparía un componente con más contenido si tenemos la idea de usar masivamente Angular Elements en nuestros proyectos.

Creo que con un poco de creatividad, se le pueden dar muchos usos a esta nueva librería, aunque no esté en nuestra primera opción.

Si te ha gustado el artículo, ¡síguenos en Twitter para estar al día de nuevos posts!

Otros ejemplos

Si deseáis más ejemplos o tutoriales para utilizar Angular Elements:

Bibliografía