Al igual que las directivas de atributo personalizadas, en Angular podemos crear nuestras propias directivas estructurales a parte de las internas de framework: *ngFor, *ngIf, *ngSwitchCase y *ngSwitchDefault.
Recordemos que las directivas estructurales tienen por objetivo añadir, manipular o eliminar elementos del DOM (Document Object Model)
Con un ejemplo veremos los pasos para su creación.
Crear una directiva estructural personalizada cuyo objetivo sea repetir n veces el texto que muestra. La sintaxis para luego utilizarla debe ser:
<span *appRepetir="5">x</span>
Crearemos primero el proyecto
ng new proyecto049
Procedemos a crear la directiva personalizada llamando a la misma 'Repetir' (recordar que Angular CLI agrega por defecto el prefijo 'app'):
ng generate directive Repetir
Se crean dos archivos y se modifica uno.
Por un lado se modifica el archivo 'app.module.ts' haciendo referencia a la clase 'RepetirDirective':
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { RepetirDirective } from './repetir.directive'; @NgModule({ declarations: [ AppComponent, RepetirDirective ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Se crea propiamente el archivo que contendrá la lógica de la directiva y tiene como nombre 'repetir.directive.ts':
import { Directive } from '@angular/core'; @Directive({ selector: '[appRepetir]' }) export class RepetirDirective { constructor() { } }
También se crea el archivo 'resaltado.directive.spec.ts' para especificar pruebas unitarias (por el momento no hemos trabajado con este tipo de archivos, no lo modificaremos ni analizaremos)
Procedemos a codicar el archivo 'repetir.directive.ts' implementando la lógica de nuestra directiva:
import { Directive, TemplateRef, ViewContainerRef, Input } from '@angular/core'; @Directive({ selector: '[appRepetir]' }) export class RepetirDirective { constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { } @Input() set appRepetir(numero: number) { for (var i = 0; i < numero; i++) this.viewContainer.createEmbeddedView(this.templateRef); } }
La diferencia con las directivas de atributo es que se inyectan al constructor dos objetos, uno de tipo TemplateRef<any> y otro de ViewContainerRef:
constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { }
Definimos un setter (método que define un valor a una propiedad) para capturar el valor que recibe la directiva:
@Input() set appRepetir(numero: number) { for (var i = 0; i < numero; i++) this.viewContainer.createEmbeddedView(this.templateRef); }
El atrituto 'this.templateRef' tiene la referencia a la etiqueta HTML (en este caso 'span') que le aplicamos la directiva, por ejemplo:
<span *appRepetir="5">x</span>
Disponemos un 'for' que se repita la cantidad de veces asignada a la directiva:
for (var i = 0; i < numero; i++)
Dentro del for mediante el objeto 'this.viewContainer' procedemos mediante el método 'createEmbeddedView' a insertar la referencia de la etiqueta HTML (por ejemplo un 'span'):
this.viewContainer.createEmbeddedView(this.templateRef);
Para entender como afecta la directiva creada debemos ver el resultado en el navegador:
Para probar la directiva personalizada modificamos el archivo 'app.component.html':
<span *appRepetir="5">x</span>
Podemos probar esta aplicación en la web aquí.
Crear una directiva estructural personalizada que imite el funcionamiento de la directiva estructural que trae Angular por defecto: *ngIf (sin la parte del else). Debe emplear la siguiente sintaxis:
<p *appSi="true">Se muestra el contenido del *appSi</p>
Crearemos primero el proyecto
ng new proyecto050
Procedemos a crear la directiva personalizada llamando a la misma 'Si' (recordar que Angular CLI agrega por defecto el prefijo 'app'):
ng generate directive Si
Se crean dos archivos y se modifica uno.
Procedemos a codicar el archivo 'si.directive.ts' implementando la lógica de nuestra directiva:
import { Directive, TemplateRef, ViewContainerRef, Input } from '@angular/core'; @Directive({ selector: '[appSi]' }) export class SiDirective { constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { } @Input() set appSi(visible: boolean) { if (visible) this.viewContainer.createEmbeddedView(this.templateRef); else this.viewContainer.clear(); } }
Dependiendo del parámetro del setter procedemos a insertar la etiqueta HTML a la que se le aplica la directiva 'appSi' o llamamos a 'clear' para dejar vacío su interior:
@Input() set appSi(visible: boolean) { if (visible) this.viewContainer.createEmbeddedView(this.templateRef); else this.viewContainer.clear(); }
Para probar la directiva personalizada modificamos el archivo 'app.component.html':
<p *ngIf="true">Se muestra el contenido del *ngIf</p> <p *appSi="true">Se muestra el contenido del *appSi</p>
Podemos probar esta aplicación en la web y ver que el código HTML generado es exactamente igual ya sea que utilicemos la directiva '*ngIf' o la directiva personalizada que hemos creado '*ngSi':
Para aprender más sobre directivas estructurales podemos consultar el código fuente de la directiva *ngIf en el repositorio oficial de Angular.