Los webcomponents se hicieron famosos por la posibilidad de reutilizar componentes de diferentes tecnologías y versiones. Pero en muchos proyectos han ampliado su alcance, haciendo proyectos reutilizables para importarlos. ¿Qué nos permite Angular Elements?
¿Qué es Angular Elements?
Angular elements are Angular components packaged as custom elements, a web standard for defining new HTML elements in a framework-agnostic way
Resumiendo, es para crear componentes reutilizables aplicando Shadow Dom. Aunque hoy en día, también se utiliza para conseguir proyectos aislados para importar en otros proyectos. Algo desaconsejado por la documentación, pero una alternativa para no utilizar iframes.
En estos proyectos vamos a analizar todas las casuísticas posibles con Angular Elements, partiendo de la versión de Angular 9.
Tendremos diferentes proyectos, aunque partiremos siempre del proyecto portal, que será el proyecto principal.
Antes de entrar a trabajar con Angular Elements, es aconsejable que leáis un poco de documentación de que son los webcomponents.
Programas
Para llevar a cabo las siguientes fases, deberéis tener instalado:
- Nodejs: https://nodejs.org/
- Git: https://git-scm.com/
- Visual Studio Code: https://code.visualstudio.com/
- Angular CLI: https://cli.angular.io/
Versiones
Pasos que vamos a realizar
Generar estructura proyecto
mkdir angular-elements-9
cd angular-elements-9
Inicializamos el git
git init
Creamos nuestra aplicación de angular y elegimos sass => scss
npx ng new portal ---routing --prefix=portal
Nos metemos dentro del proyecto
cd portal
Instalamos las dependencias, con npm o yarn
yarn install
// o
npm install
Probamos que la aplicación funciona correctamente
npm run start
Abrimos el navegador, y ponemos la ruta que nos indica la terminal: http://localhost:4200/
Podemos ver la aplicación de angular ejecutada.
Añadimos la dependencia de Angular Elements
ng add @angular/elements
Esto nos añadirá algunas modificaciones en el proyecto, además de la dependencia en el package.json
«@angular/elements»: «^9.1.9»,
Llegado a este punto, ya tenemos nuestra aplicación principal configurada, para ir creando los angular element y posteriormente importarlos.
Existen muchas formas de utilización de angular elements en un proyecto. En este caso, para hacerlo más sencillo, vamos a copiar las carpetas dist
de cada proyecto spa-numero en la carpeta assets de nuestro proyecto portal, y los imporemos de ahí. Algunas de las otras posibilidades serían mediante dependencias de package.json, CDN, por configuración, etc…
Vamos a crear un menú con lazy loading para ir añadiendo las diferentes SPA de webcomponents que vayamos generando.
Desde la aplicación portal, lanzamos el siguiente comando:
ng g module pages/section-spa-one --route section-spa-one --module app.module
Este comando nos generará un module con su componente respectivo, donde luego importaremos el webcomponent generado de SPA One.
Luego en de app.component.html, añadimos el menú, para que tengamos la opción de routing.
Portal
Toda la documentación del portal y pasos realizados para su generación, están en su propio readme.
Generamos SPA-ONE
Generamos la aplicación spa-one
ng new spa-one --routing --prefix=spa-one
La documentación de dicha aplicación, y como generarla, está en su propio readme.
Vamos a convertir esta aplicación Spa One en un webcomponent que importaremos desde la aplicación Portal.
Pasos
Añadimos la dependencia de Angular Elements
ng add @angular/elements
Esto nos añadirá algunas modificaciones en el proyecto, además de la dependencia en el package.json
"@angular/elements": "^9.1.9",
Instalamos librería para compilar
Instalamos la librería que utilizaremos para compilar el proyecto en Angular Elements. La instalaremos en formato general, pero también podríamos instalarla en uno de los subproyectos.
También tenemos la opción de generar un script para concatenar todos los JS, pero esta librería nos permitirá configuraciones avanzadas, que en otros artículos analizaremos.
ng add ngx-build-plus
Actualizamos app.module.ts, para registrar nuestro componente como un custom element. Además eliminaremos el componente de la propiedad Bootstrap, dado que lo ejecturemos a través del ngDoBoostrap.
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,
AppRoutingModule
],
providers: [],
bootstrap: []
})
export class AppModule {
constructor(private injector: Injector) {
const el = createCustomElement(AppComponent, { injector });
customElements.define('spa-one-elements', el);
}
ngDoBootstrap() {}
}
Actualizamos el app.component.ts, para convertir nuestro custom element en un webcomponent, al aplicarle el shadow dom.
import { Component } from '@angular/core';
@Component({
selector: 'spa-one-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsultion.Native
})
export class AppComponent {
title = 'spa-one';
}
Actualizamos el package.json, para generar el build. Con este comando conseguiremos tener solo un build, además de quitar los hash de los bundldes.
"build:ele": "ng build --prod --output-hashing none --single-bundle true",
Una vez generado, nos vamos con la terminal a la carpeta ./dist/spa-one y actualizamos el contenido para añadir el custom-element, en lugar del app-root
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SpaOne</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<spa-one-elements></spa-one-elements>
<script src="polyfills-es5.js" nomodule defer></script>
<script src="polyfills-es2015.js" type="module"></script>
<script src="main-es2015.js" type="module"></script>
<script src="main-es5.js" nomodule defer></script>
</body>
</html>
Luego, en la terminal, levantamos un servidor temporal, para comprobar su visualización. Luego abrimos nuestro navegador en la ruta http://localhost:9080/
.
npx static-server
En el caso de que quisiésemos un webcomponent, deberíamos añadir una propiedad al app.component.ts para añadirle el shadown.
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'spa-one-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom
})
export class AppComponent {
title = 'spa-one';
}
Como podemos ver en la captura de pantalla, si vamos al depurador, veremos que ahora tenemos #shadow-root.
Ya tenemos la gran parte terminada. Ahora solo nos faltaría importar nuestro proyecto webcomponent en el portal.
Importando webcomponent
Deberemos copiar la carpeta dist/spa-one de nuestro proyecto, y llevarla a assets del proyecto portal.
La forma básica de añadir un webcomponent en nuestra aplicación, será importándolo en el componente de nuestra aplicación donde queramos mostrarlo y añadir la etiqueta html.
// section-spa-one.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SectionSpaOneRoutingModule } from './section-spa-one-routing.module';
import { SectionSpaOneComponent } from './section-spa-one.component';
import '../../../assets/spa-one/main-es2015';
@NgModule({
declarations: [SectionSpaOneComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [CommonModule, SectionSpaOneRoutingModule],
})
export class SectionSpaOneModule {}
// section-spa-one.component.html
<p>section-spa-one works!</p>
<spa-one-elements></spa-one-elements>
Ventajas:
- Fácil de aplicar
- Rápido
Desventajas
- No podemos controlar si hay algún error en el webcomponent
- No podemos gestionar su carga
- No podemos generarlo dinámicamente