Los web components son como widgets que podemos usar en una aplicación web. Imaginemos por ejemplo un portal de noticias, podriamos tener un web component para mostrar las últimas noticias, otro web component para realizar una encuesta de un tema de actualidad, y otro para mostrar los resultados de la quiniela de la última jornada, por ejemplo.
Con Web Components puedes hacer casi cualquier cosa que pueda ser hecha con HTML, CSS y JavaScript, y puede ser un componente portable que puede ser reutilizado fácilmente.
En resumen, los web component son pequeños bloques de código, de diferentes tecnologías (javascript, css, html) que están encapsulados de tal forma que cada uno está aislado respecto los demás.
Uno de los problemas sin embargo cuando usamos componentes en un portal es que los estilos de un componente no entren en conflicto con los estilos de otro. Para evitar esto, los web components utilizan un mecanismo que se llama Shadow DOM, que consiste en un DOM encapsulado que vive dentro del DOM principal.
Para lograr interoperabilidad o resolución de conflictos entre componentes se inventó lo que en Angular se conoce como View Encapsulation.
Hay tres tipos de encapsulación de vistas en Angular:
- Emulated
- Native
- None
Vamos a ver cada uno de estos tipos mediante un sencillo ejemplo. Vamos a crear tres componentes. Cada uno de ellos tendrá un botón y a aplicaremos los distintos tipos de encapsulación a cada uno de los componentes para ver los resultados.
Los componentes que vamos a crear se llamarán button-close.component.ts, button-open.component.ts y button-print.component.ts
El primer tipo de encapsulacion que vamos a ver es "emulated" que es el que viene por defecto. Para ello lo especificamos en la anotación "component" mediante el metadata encapsulation (encapsulation: ViewEncapsulation.Emulated) del ButtonPrintComponent tal como se muestra a continuación:
@Component({
selector: 'button-print',
template: `Button print`,
styleUrls: [ './button-print.component.css' ],
encapsulation: ViewEncapsulation.Emulated
})
El resto de componentes lo vamos a poner igualmente con este tipo de encapsulación para ver el resultado, luego nos quedaría algo como esto:
@Component({
selector: 'button-close',
template: `....`,
styleUrls: [ './button-close.component.css' ],
encapsulation: ViewEncapsulation.Emulated
})
@Component({
selector: 'button-open',
template: `...`,
styleUrls: [ './button-open.component.css' ],
encapsulation: ViewEncapsulation.Emulated
})
Vamos a usar Bulma como framework css, y por cuestión de simplicidad vamos a incluir un cdn a bulma dentro del index.html (mala práctica, pero en este caso lo hago por simplificar la entrada)
El código fuente del componente ButtonPrintComponent es el que se muestra a continuación:
El resto de botones tendría un aspecto similar.
Ahora vamos a colocar a la clase "button" de cada uno de los componentes, dentro de su css particular de cada uno de ellos un borde de color distinto para cada uno de ellos.
Es decir, en el button-print.component.css creamos la clase:
.button.is-primary {
border:2px solid red;
}
para la clase del botón open:
.button.is-primary {
border:2px solid blue;
}
y para el botón close:
.button.is-primary {
border:2px solid black;
}
El resultado es el que podemos observar en el siguiente pantallazo:
Como podemos observar los estilos de un botón no han entrado en conflicto con los de los otros botones.
Si nos fijamos en el inspector de clases, vemos que se ha creado una clase nghost-cx para cada uno de los componentes.
Lo de host es un modo de emulación que es lo que precisamente se conoce como shadow DOM
Como resuelve Angular que los estilos de un componente no entren en conflicto con los de los otros? Si nos volvemos a fijar en el inspector vemos que para cada uno de los componentes se ha creado una propiedad "_ngcontent-cx" y una clase asociada a la misma, es decir, una clase .button.is-primary[_ngcontent-c1], .button.is-primary[_ngcontent-c2] y .button.is-primary[_ngcontent-c3] que representa a cada uno de los componentes que hemos creado.
Lo vemos mejor en el siguiente pantallazo:
Las clases que hemos mencionado anteriormente (.button.is-primary[_ngcontent-c1],.....) Angular las crea y las mete dentro del <head>.
De nuevo lo podemos ver mejor en el siguiente pantallazo:
Si cambiamos el tipo de encapsulación a uno de los botones, por ejemplo al primero, al de "print" por native, vemos que cambia y a a coger el shadow DOM del browser.
Lo que hace Angular es que ha metido todos los estilos de ese botón en algo que se llama "shadow-root". Este shadow-root es como si se generara una capsula donde se meten todos los estilos, pero no sólo los estilos del botón "print", si no los de ese botón y los del resto de componentes.
Este tipo de encapsulación no es soportado por todos los navegadores por lo que antes de usarlo hay que tener en cuenta que navegadores soportan dicha encapsulación. (Por ejemplo, IE0-IE11 no soportan este tipo de encapsulación)
Por último si usamos la encapsulación "none" lo que pasa es que se genera un estilo "global". Para verlo, vamos a poner en el css del último componente (ButtonCloseComponent) un color de fondo rojo:
(button-close.component.css)
.button.is-primary {
border:2px solid red;
background-color: red;
}
Esto lo que hace es crear un estilo global tal como puede observarse en la siguiente imagen:
Luego en este caso, al haberlo aplicado sobre el último componente, afectaría al resto de componentes pudiendo modificar su aspecto. Lógicamente este tipo de encapsulación es la menos recomendable, al menos en lo que se conoce como DOM components, (componentes sin lógica) ya que se pueden generar conflictos con otros componentes.
En la entrada de hoy hemos visto los tipos de encapsulamiento que trae Angular y se ha visto un ejemplo de uso de cada uno de ellos y explicado las diferencias entre ellos.
El código fuente esta disponible en:
https://view-encapsulation-in-angular.stackblitz.io
Hasta la próxima entrada :-)