En este artículo veremos qué es esto de «Directive composition API» (feature incluída a partir de Angular 15) para resolver de otra manera cómo manipular nuestro host del componente explicado en el artículo Convierte el elemento host del componente invisible.

Si bien las soluciones del mencionado artículo son válidas, la verdad es que si queremos aplicarlo a todos nuestros componentes, estaríamos repitiendo el mismo código una y otra vez rompiendo el concepto de DRY (Don’t Repeat Yourself). Es por eso que la solución que vamos a proponer aquí es extraer esa modificación del host a una directiva o si tuviéramos varias modificaciones sería llevar cada modificación a una directiva diferente (pensando en la «S» de los principios SOLID).

Añadiendo directivas al componente

Mediante la propiedad «hostDirectives» del decorador @Component({ ..., hostDirectives: [], ...}) podremos incluir todas las directivas que necesitemos para poder transformar nuestro host, por lo tanto vemos desde un principio que es una lista de directivas especificada de la siguiente manera:

hostDirectives?: (Type<unknown> | {
    directive: Type<unknown>;
    inputs?: string[];
    outputs?: string[];
})[]

Es decir, tenemos que incluir por cada posición de la lista qué directiva queremos usar mediante la propiedad «directive«, si necesita algunos valores de entrada que se definirán en la propiedad «inputs«, y si emite alguna salida que se definirán a través de «outputs«.

Hay que tener en cuenta lo siguiente:

  • La/s directiva/s debe/n de ser standalone.
  • Se aplican las directivas estáticamente en el momento de compilación, así que no se pueden añadir de forma dinámica.
  • Se ignora el selector dado que ya las incluimos en hostDirectives.

Ejemplo de uso

Tomaremos como ejemplo el caso de uso mencionado con anterioridad, esto es: Necesitamos añadir al host el atributo CSS «display: contents«. Es una directiva muy simple que no necesita ni entradas (inputs) ni produce salidas (outputs).

Creación de la directiva

Tan sencillo como declarar la directiva a la que llamaremos «InvisibleComponentDirective» con selector «[afInvisibleFlex]». Como en el anterior caso, podemos incluir el estilo con el atributo o llamar una clase CSS:

import { Directive } from '@angular/core';

@Directive({
    selector: '[afInvisibleFlex]',
    standalone: true,
    host: {
        'style': 'display: contents'
        'class': af_element--host
    }
})
export class InvisibleComponentDirective {}

La clase puede estar en alguna librería de estilos o tema:

.af_element--host {
  display: contents;
}

Aplicación de la directiva

En nuestro componente de botón:

import { InvisibleComponentDirective } from '@af/utils';

@Component({
    selector: 'af-text-button',
    standalone: true,
    imports: [CommonModule],
    templateUrl: './text-button.component.html',
    styleUrl: './text-button.component.scss',
    hostDirectives: [{ directive: InvisibleComponentDirective }]
})
export class TextButtonComponent {
    @Input() label = '';
    @Input() type: buttonType = 'primary';
}

Rendimiento

Si bien a nivel de código separa conceptos, clarifica el código y sería más fácil hacer los tests, siempre hay que velar por el rendimiento o performance de la aplicación si no se tiene cuidado. En este caso es una directiva sencilla, pero siempre se puede complicar teniendo varias directivas (y por lo tanto habría varias creaciones de objetos) que se usan en múltiples elementos multiplicando esos objetos en memoria siempre teniendo en cuenta también el ciclo de vida de cada una de ellas y del orden de ejecución.

Resumen

Hemos visto otra manera de manipular nuestro elemento host del componente mediante lo que Angular denomina «Directive composition API» utilizando el caso de uso de un artículo anterior. Si bien es muy fácil de utilizar y facilita la comprensión del código, siempre hay que velar también por el rendimiento de la aplicación.