====== 3. Creación de componentes: Atributos ====== Hemos visto como crear un componente que genera todo el HTML: Hola mundo Sin embargo este componente tiene dos problemas. * No podemos elegir si genera un '''' o '' ===== @HostBinding ===== Pues lo que queremos es que nuestro componente modifique el atributo ''class'' de nuestro **host**. Pues para ello usamos el decorador de Angular [[https://angular.dev/api/core/HostBinding|@HostBinding(hostPropertyName?: string)]] import {Component, Input, ViewEncapsulation, HostBinding} from '@angular/core'; @Component({ selector: 'button[boton], a[boton]', template: '', styleUrl: './boton.scss', encapsulation: ViewEncapsulation.None }) export class Boton implements OnChanges { @Input() funcion:'normal' | 'alternativa' | 'peligrosa'='normal'; @HostBinding('class') get clazz(): string { return `boton funcion-${this.funcion}`; } } El decorador ''@HostBinding('class')'' hace que el valor de la propiedad ''class'' siempre sea el valor que tiene la propiedad ''clazz'' Recuerda que ''clazz'' es una propiedad solo que calculada a través de la función ''clazz()'' get clazz(): string { return `boton funcion-${this.funcion}`; } El problema aquí el que no podríamos tener valores en ''class'' en el **Host** ya que la propiedad ''clazz'' elimina lo que hay y pone los datos de únicamente nuestras clases. Es decir que el siguiente ejemplo no funcionaría, ya que eliminaría ''g--bg-color-verde-5'': Hola mundo Aunque si que funcionaría el siguiente ya que nuestro componente no modifica ni el atributo ''routerLink'' ni ''style'': Hola mundo Así que ¿como lo arreglamos? Pues retornando un objeto con las clases que vamos a añadir en vez de un ''string'' y de esa forma no se modifica lo que ya hay. import {Component, Input, ViewEncapsulation, HostBinding} from '@angular/core'; @Component({ selector: 'button[boton], a[boton]', template: '', styleUrl: './boton.scss', encapsulation: ViewEncapsulation.None }) export class Boton { @Input() funcion:'normal' | 'alternativa' | 'peligrosa'='normal'; @HostBinding('class') get clazz(): Record { return { 'boton': true, 'funcion--normal': this.funcion === 'normal', 'funcion--alternativa': this.funcion === 'alternativa', 'funcion--peligrosa': this.funcion === 'peligrosa', }; } } Ahora retornamos un objeto con las clases CSS que podrían haber y son un booleano indicando si está finalmente o no, dejando sin tocar el resto de clases. Fíjate en que el tipo de datos de la propiedad ''clazz'' es ''Record'' que es simplemente como decir que retorna ''{ [key: string]: boolean }''. Que significa que es un objeto cuyas claves son un ''string'' y cuyos valores son un ''boolean''. La definición de ''Record'' es exactamente la siguiente: type Record = { [P in K]: T; }; Mas información: * [[https://blog.logrocket.com/typescript-record-types/|Level up your TypeScript with Record types]] ===== ViewEncapsulation ===== Nos falta por explicar el valor de ''encapsulation'' que depende del enumerado [[https://angular.dev/api/core/ViewEncapsulation|ViewEncapsulation]] import {Component, Input, ViewEncapsulation, HostBinding} from '@angular/core'; @Component({ selector: 'button[boton], a[boton]', template: '', styleUrl: './boton.scss', encapsulation: ViewEncapsulation.None }) export class Boton { @Input() funcion:'normal' | 'alternativa' | 'peligrosa'='normal'; @Input() importancia : 'primaria' | 'secundaria' | 'terciaria'="primaria"; @HostBinding('class') get clazz(): Record { return { 'boton': true, 'funcion--normal': this.funcion === 'normal', 'funcion--alternativa': this.funcion === 'alternativa', 'funcion--peligrosa': this.funcion === 'peligrosa', }; } } El enumerado [[https://angular.dev/api/core/ViewEncapsulation|ViewEncapsulation]] indica la visibilidad de los estilos CSS de un componente. * ''ViewEncapsulation.None'' : Los estilos CSS del componente son visibles desde cualquier otra parte de la aplicación es como hacer **los estilos CSS como públicos**. * ''ViewEncapsulation.ShadowDom'' : Los estilos CSS del componente solo se pueden ver en las etiquetas del propio ''template'' del componente es como hacer **los estilos CSS como privados**. Para implementarlo se usa el API de Shadow DOM del navegador. * ''ViewEncapsulation.Emulated'' : Los estilos CSS del componente solo se pueden ver en las etiquetas del propio ''template'' del componente es como hacer **los estilos CSS como privados**. Para implementarlo se ocupa Angular sin el soporte del navegador. Este es el valor por defecto. Existe ''ViewEncapsulation.Emulated'' además de ''ViewEncapsulation.ShadowDom'' ya que el soporte del API de Shadow DOM en el navegador ha sido muy malo durante mucho tiempo. ¿Porque es importante ahora esta propiedad y porque la hemos cambiado a ''ViewEncapsulation.None''? Resulta que si ponemos el valor por defecto de ''ViewEncapsulation.Emulated'' o su alternativa ''ViewEncapsulation.ShadowDom'' los estilos solo se pueden usar en los tag que hay dentro de ''template''. Sin embargo , con el cambio que hemos hecho , los estilos CSS se van a usar ahora fuera de los tags de ''template'' porque los vamos a usar en **Host**, así que es necesario hacer los estilos CSS públicos, no //encapsularlos// y por lo tanto usar ''ViewEncapsulation.None'' Vale, pero ahora tenemos un problema. Tenemos las clases CSS ''funcion--normal'' tanto en '''' como en ''''. Y son distintos. Por lo tanto debemos cambiar los estilos de los componentes para que no //choquen// entre los distintos componentes. Para evitar que choquen los nombres de los estilos CSS entre los distintos componentes se usan las nomenclaturas de: * [[https://getbem.com/|BEM]] * [[https://suitcss.github.io/|SUIT CSS]] Más adelante en el curso se explicará en que consisten estas nomenclaturas. Pero los estilos ahora quedan de la siguiente forma: .boton { font-family: sans-serif; font-size: 16px; padding: 6px; border-radius: 6px; border-width: 1px; border-style: solid; display: inline-block; cursor: pointer; text-decoration: none; border-color: #0056b8; background-color: #0056b8; color: #ffffff; } .boton--funcion-normal { border-color: #0056b8; background-color: #0056b8; color: #ffffff; } .boton--funcion-alternativa { border-color: #ed8936; background-color: #ed8936; color: #ffffff; } .boton--funcion-peligrosa { border-color: #c53030; background-color: #c53030; color: #ffffff; } Y el código queda finalmente así: import {Component, Input, ViewEncapsulation, HostBinding} from '@angular/core'; @Component({ selector: 'button[boton], a[boton]', template: '', styleUrl: './boton.scss', encapsulation: ViewEncapsulation.None }) export class Boton { @Input() funcion:'normal' | 'alternativa' | 'peligrosa'='normal'; @HostBinding('class') get clazz(): Record { return { 'boton': true, 'boton--funcion-normal': this.funcion === 'normal', 'boton--funcion-alternativa': this.funcion === 'alternativa', 'boton--funcion-peligrosa': this.funcion === 'peligrosa', }; } }