Herramientas de usuario

Herramientas del sitio


clase:daw:diw:1eval:tema03.b

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
clase:daw:diw:1eval:tema03.b [2025/11/23 20:23] – [@HostBinding] Lorenzoclase:daw:diw:1eval:tema03.b [2026/10/23 23:04] (actual) – Reorganización del módulo: contenido movido a las páginas nuevas de clase:daw:diw claude
Línea 1: Línea 1:
-====== 3. Creación de componentes: Atributos ====== 
-Hemos visto como crear un componente que genera todo el HTML: 
- 
-<sxh html> 
-<boton funcion="peligrosa" (onClick)="alerta()"  >Hola mundo</boton> 
-</sxh> 
- 
-Sin embargo este componente tiene dos problemas.  
-  * No podemos elegir si genera un ''<nowiki><a></nowiki>'' o ''<nowiki><button></nowiki>'' 
-  * No es tan personlizable porque no podemos cambiar nada sino es modificando el propio componente botón. 
- 
- 
-Para mejorar eso, podríamos modificar el componente botón y hacer que solo fuera un atributo de la siguiente forma: 
- 
-<sxh html> 
-<a boton funcion="peligrosa" (click)="alerta()"  >Hola mundo</a> 
-</sxh> 
- 
-¿Que hemos ganado ahora? Pues que tenemos ahora toda la potencia del tag ''<nowiki><a></nowiki>''. Y podemos hacer cosas como la siguientes sin modificar el componente ''<nowiki><boton></nowiki>'': 
- 
-<sxh html> 
-<a boton funcion="peligrosa" routerLink="/login" style="padding:var(-mlt-sys-padding-2)" class="g--background-color-verde-5">Hola mundo</a> 
-</sxh> 
- 
-Es decir podemos añadir todos los atributos que habría en un ''<nowiki><a></nowiki>'' sin perder la funcionalidad de un ''<nowiki><boton></nowiki>''. 
- 
- 
- 
-Empecemos desde el principio del '''': 
- 
-<sxh ts;title:boton.ts; highlight: [4, 5,7]> 
-import {Component,  Input, ViewEncapsulation} from '@angular/core'; 
- 
-@Component({ 
-  selector: 'button[boton], a[boton]', 
-  template: '<ng-content></ng-content>', 
-  styleUrl: './boton.scss', 
-  encapsulation: ViewEncapsulation.None 
-}) 
-export class Boton   { 
-  @Input() funcion:'normal' | 'alternativa' | 'peligrosa'='normal'; 
- 
- 
-} 
-</sxh> 
- 
-  * El valor del ''selector'' ahora es ''button[boton], a[boton]''. Y es para decir que se aplica este componente cuando un tag ''<nowiki><a></nowiki>'' o un ''<nowiki><boton></nowiki>'' tiene el atributo ''boton''. 
-  * Ya no usamos ''templateUrl'' sino ''template'' que es paraa indicar directamente la plantilla en vez referenciar un fichero. Y lo hacemos porque ahora la plantilla es solo ''<nowiki>'<ng-content></ng-content></nowiki>'' y no necesitamos un fichero solo para eso. 
-  * Por último decimos en ''encapsulation'' que ahora los estilos ya no son privados a nuestro componente sino que son "públicos". Eso nos genera un problema porque tenemos las clases CSS ''funcion--normal'' tanto en ''<nowiki><boton></nowiki>'' como en ''<nowiki><panel></nowiki>''. Y son distintos 
- 
-===== Etiqueta Host ===== 
-¿Que queremos que haga realmente nuestro componente? Pues simplemente es establecer el valor del atributo ''class'', pero en el tag que ha escrito el usuario .  
-Es decir que el el que está "fuera" en la página HTML. A ese tag se le llama **Host**. 
- 
-Veamos unos ejemplo de que es **Host** 
- 
- 
-  * **Host** hace referencia a la etiqueta ''<nowiki><a></nowiki>'' 
-<sxh html> 
-<a boton funcion="peligrosa" routerLink="/login" style="padding:var(-mlt-sys-padding-2)" class="g--background-color-verde-5">Hola mundo</a> 
-</sxh> 
- 
-  * **Host** hace referencia a la etiqueta ''<nowiki><button></nowiki>'' 
-<sxh html> 
-<button boton funcion="peligrosa" >Hola mundo</button> 
-</sxh> 
- 
- 
- 
-===== @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)]] 
- 
-<sxh ts;title:boton.ts; highlight: [12-15]> 
-import {Component,  Input, ViewEncapsulation, HostBinding} from '@angular/core'; 
- 
-@Component({ 
-  selector: 'button[boton], a[boton]', 
-  template: '<ng-content></ng-content>', 
-  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}`; 
-  } 
- 
-} 
-</sxh> 
- 
-El decorador ''@HostBinding('class')'' hace que el valor de la propiedad ''class'' siempre sea el valor que tiene la propiedad ''clazz'' 
- 
-<note tip>   
-Recuerda que ''clazz'' es una propiedad solo que calculada a través de la función ''clazz()'' 
-<sxh ts> 
-get clazz(): string { 
-  return `boton funcion-${this.funcion}`; 
-} 
-</sxh> 
-</note> 
- 
- 
-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'': 
- 
-<sxh html> 
-<a boton  class="g--background-color-verde-5">Hola mundo</a> 
-</sxh> 
- 
-Aunque si que funcionaría el siguiente ya que nuestro componente no modifica ni el atributo ''routerLink'' ni ''style'': 
- 
-<sxh html> 
-<a boton  routerLink="/login" style="padding:var(--mlt-sys-padding-2)">Hola mundo</a> 
-</sxh> 
- 
-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. 
- 
-<sxh typescript; title:boton.ts;highlight: [13-22]> 
-import {Component, Input, ViewEncapsulation, HostBinding} from '@angular/core'; 
- 
-@Component({ 
-  selector: 'button[boton], a[boton]', 
-  template: '<ng-content></ng-content>', 
-  styleUrl: './boton.scss', 
-  encapsulation: ViewEncapsulation.None 
-}) 
-export class Boton   { 
-  @Input() funcion:'normal' | 'alternativa' | 'peligrosa'='normal'; 
- 
- 
-  @HostBinding('class') 
-  get clazz(): Record<string, boolean> { 
-    return { 
-      'boton': true, 
- 
-      'funcion--normal': this.funcion === 'normal', 
-      'funcion--alternativa': this.funcion === 'alternativa', 
-      'funcion--peligrosa': this.funcion === 'peligrosa', 
-    }; 
-  } 
- 
-} 
-</sxh> 
- 
-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. 
- 
-<note tip>   
-Fíjate en que el tipo de datos de la propiedad ''clazz'' es ''Record<string, boolean>'' 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: 
-<sxh ts> 
-type Record<K extends keyof any, T> = { 
-    [P in K]: T; 
-}; 
-</sxh> 
- 
-Mas información: 
-  * [[https://blog.logrocket.com/typescript-record-types/|Level up your TypeScript with Record types]] 
- 
-</note> 
- 
-===== ViewEncapsulation ===== 
-Nos falta por explicar el valor de ''encapsulation'' que depende del enumerado [[https://angular.dev/api/core/ViewEncapsulation|ViewEncapsulation]] 
- 
-<sxh typescript; title:boton.ts;highlight: [7]> 
-import {Component, Input, ViewEncapsulation, HostBinding} from '@angular/core'; 
- 
-@Component({ 
-  selector: 'button[boton], a[boton]', 
-  template: '<ng-content></ng-content>', 
-  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<string, boolean> { 
-    return { 
-      'boton': true, 
- 
-      'funcion--normal': this.funcion === 'normal', 
-      'funcion--alternativa': this.funcion === 'alternativa', 
-      'funcion--peligrosa': this.funcion === 'peligrosa', 
- 
-    }; 
-  } 
- 
-} 
-</sxh> 
- 
-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 ''<nowiki><boton></nowiki>'' como en ''<nowiki><panel></nowiki>''. 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: 
- 
-<sxh css;title:boton.scss> 
-.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; 
-} 
-</sxh> 
- 
-Y el código queda finalmente así: 
- 
-<sxh ts, title:boton.ts; highlight: [15,17-19]> 
-import {Component, Input, ViewEncapsulation, HostBinding} from '@angular/core'; 
- 
-@Component({ 
-  selector: 'button[boton], a[boton]', 
-  template: '<ng-content></ng-content>', 
-  styleUrl: './boton.scss', 
-  encapsulation: ViewEncapsulation.None 
-}) 
-export class Boton   { 
-  @Input() funcion:'normal' | 'alternativa' | 'peligrosa'='normal'; 
- 
-  @HostBinding('class') 
-  get clazz(): Record<string, boolean> { 
-    return { 
-      'boton': true, 
- 
-      'boton--funcion-normal': this.funcion === 'normal', 
-      'boton--funcion-alternativa': this.funcion === 'alternativa', 
-      'boton--funcion-peligrosa': this.funcion === 'peligrosa', 
- 
-    }; 
-  } 
- 
-} 
-</sxh> 
- 
  
clase/daw/diw/1eval/tema03.b.1763925801.txt.gz · Última modificación: por Lorenzo