tutoriales.com

Componentes de Contenido Reutilizables en Angular: ¡Crea Módulos Compartidos y Bibliotecas!

Este tutorial te guiará a través del proceso de creación y gestión de componentes, directivas y servicios reutilizables en Angular. Aprenderás a estructurar tu código en módulos compartidos y, más allá, a empaquetar estos elementos en bibliotecas que puedes publicar y distribuir. Optimiza tu flujo de trabajo y mejora la mantenibilidad de tus proyectos.

Intermedio20 min de lectura6 views
Reportar error

✨ Introducción: La Reutilización como Clave del Éxito en Angular

En el desarrollo de aplicaciones Angular, la reutilización de código no es solo una buena práctica; es una estrategia fundamental para construir aplicaciones robustas, escalables y fáciles de mantener. Imagina tener que recrear el mismo botón de navegación, formulario de entrada o lógica de autenticación en cada proyecto o incluso en diferentes secciones de una misma aplicación. Sería una pesadilla de duplicación y mantenimiento.

Aquí es donde entra en juego la potencia de Angular para crear componentes, directivas y servicios reutilizables. Este tutorial te sumergirá en el arte de construir estos bloques de construcción modulares, organizarlos en módulos compartidos y, finalmente, empaquetarlos como bibliotecas publicables.


🎯 Objetivos de Aprendizaje

Al finalizar este tutorial, serás capaz de:

  • Entender la importancia de la reutilización de código en Angular.
  • Crear componentes, directivas y servicios genéricos y configurables.
  • Organizar elementos reutilizables en un SharedModule.
  • Comprender la diferencia entre un SharedModule y una biblioteca Angular.
  • Generar una biblioteca Angular (Angular Library) desde cero.
  • Publicar una biblioteca Angular en npm (o un registro privado).
  • Consumir componentes de tu propia biblioteca en otras aplicaciones Angular.
📌 Nota: Este tutorial asume que tienes conocimientos básicos de Angular (componentes, servicios, módulos y CLI).

📦 La Base: Componentes, Directivas y Servicios Reutilizables

Antes de pensar en módulos compartidos o bibliotecas, debemos entender cómo diseñar nuestros elementos individuales para que sean reutilizables.

Componentes Reutilizables: Diseño con Flexibilidad

Un componente es reutilizable si puede adaptarse a diferentes contextos con mínimas modificaciones. Esto se logra mediante:

  • Entradas (@Input()): Para recibir datos del componente padre y personalizar su comportamiento o contenido.
  • Salidas (@Output()): Para notificar al componente padre sobre eventos que ocurren dentro del componente (clicks, cambios, etc.).
  • Proyección de Contenido (<ng-content>): Para permitir que el componente padre inserte su propio HTML dentro del template del componente hijo.

Ejemplo: Componente AppButton genérico

Crearemos un botón personalizable que puede cambiar su texto, color y manejar eventos de click.

  1. Generar el componente:
ng generate component shared/app-button
  1. app-button.component.ts:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'success';

@Component({
selector: 'app-app-button',
template: `
<button [ngClass]="getButtonClasses()" (click)="onClick()">
<ng-content></ng-content>
{{ text }}
</button>
`,
styles: [`
.app-button {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s ease;
}
.app-button-primary {
background-color: #007bff;
color: white;
}
.app-button-secondary {
background-color: #6c757d;
color: white;
}
.app-button-danger {
background-color: #dc3545;
color: white;
}
.app-button-success {
background-color: #28a745;
color: white;
}
.app-button:hover {
opacity: 0.9;
}
`]
})
export class AppButtonComponent implements OnInit {
@Input() text: string = 'Click Me';
@Input() variant: ButtonVariant = 'primary';
@Input() disabled: boolean = false;
@Output() buttonClick = new EventEmitter<void>();

constructor() { }

ngOnInit(): void { }

getButtonClasses(): string[] {
return [`app-button`, `app-button-${this.variant}`];
}

onClick(): void {
if (!this.disabled) {
this.buttonClick.emit();
}
}
}
  1. Uso en un componente padre (app.component.html):
<app-app-button text="Guardar" variant="success" (buttonClick)="onSave()">
<span class="material-icons">save</span>
</app-app-button>
<app-app-button text="Cancelar" variant="secondary" (buttonClick)="onCancel()"></app-app-button>
<app-app-button variant="danger" [disabled]="true">Eliminar</app-app-button>
Observa cómo usamos `<ng-content>` para insertar un icono de `material-icons` dentro del botón `Guardar`.

Directivas Reutilizables: Añadiendo Comportamiento

Las directivas son ideales para añadir comportamiento o manipular el DOM de elementos existentes. Una directiva reutilizable debe ser lo suficientemente genérica para aplicarse a varios elementos sin acoplamiento fuerte.

Ejemplo: Directiva Highlight genérica

Esta directiva permitirá resaltar un elemento con un color específico al pasar el ratón por encima.

  1. Generar la directiva:
ng generate directive shared/highlight
  1. highlight.directive.ts:
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';

@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input('appHighlight') highlightColor: string = 'yellow';
private originalBackgroundColor: string;

constructor(private el: ElementRef, private renderer: Renderer2) {
this.originalBackgroundColor = this.el.nativeElement.style.backgroundColor;
}

@HostListener('mouseenter') onMouseEnter() {
this.changeBackgroundColor(this.highlightColor);
}

@HostListener('mouseleave') onMouseLeave() {
this.changeBackgroundColor(this.originalBackgroundColor);
}

private changeBackgroundColor(color: string) {
this.renderer.setStyle(this.el.nativeElement, 'background-color', color);
}
}
  1. Uso en un componente (app.component.html):
<p appHighlight="lightblue">Pasa el ratón por aquí para resaltar en azul claro.</p>
<div appHighlight="#ffcc00">Este div se resaltará en ámbar.</div>

Servicios Reutilizables: Lógica de Negocio y Utilidades

Los servicios son perfectos para encapsular lógica de negocio, manejo de datos o utilidades que pueden ser utilizadas por múltiples componentes o directivas. La inyección de dependencia de Angular facilita su reutilización.

Ejemplo: Servicio Logger genérico

Un servicio simple para loguear mensajes en la consola con diferentes niveles.

  1. Generar el servicio:
ng generate service shared/logger
  1. logger.service.ts:
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root' // Disponible en toda la aplicación
})
export class LoggerService {
log(message: string): void {
console.log(`[LOG]: ${message}`);
}

warn(message: string): void {
console.warn(`[WARN]: ${message}`);
}

error(message: string): void {
console.error(`[ERROR]: ${message}`);
}
}
  1. Uso en un componente (app.component.ts):
import { Component } from '@angular/core';
import { LoggerService } from './shared/logger.service'; // Asegúrate de la ruta correcta

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private logger: LoggerService) { }

onSave(): void {
this.logger.log('Datos guardados exitosamente.');
// Lógica para guardar
}

onCancel(): void {
this.logger.warn('Operación cancelada por el usuario.');
// Lógica para cancelar
}

simulateError(): void {
this.logger.error('Error al procesar la solicitud.');
}
}

🤝 Módulos Compartidos (SharedModule): Organizando la Reutilización Local

Una vez que tienes varios componentes, directivas y pipes (otro tipo de elemento reutilizable no cubierto aquí en profundidad, pero que sigue la misma lógica) que son genéricos y quieres usarlos en diferentes módulos dentro de la misma aplicación, la mejor práctica es agruparlos en un SharedModule.

Un SharedModule es un módulo Angular que declara y exporta los componentes, directivas y pipes que serán reutilizados por otros módulos. También puede importar módulos comunes de Angular como CommonModule o FormsModule para que los elementos exportados funcionen correctamente.

Pasos para crear un SharedModule

  1. Generar el módulo:
ng generate module shared --flat
El flag `--flat` evita que se cree una carpeta para el módulo, lo que es común para `SharedModule`.

2. shared.module.ts:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; // Si tus componentes reutilizables usan ngModel

// Importa y declara tus componentes, directivas y pipes reutilizables
import { AppButtonComponent } from './app-button/app-button.component';
import { HighlightDirective } from './highlight.directive';
// import { MyCustomPipe } from './my-custom.pipe'; // Si tienes pipes

@NgModule({
declarations: [
AppButtonComponent,
HighlightDirective,
// MyCustomPipe
],
imports: [
CommonModule,
FormsModule // Si es necesario
],
exports: [
CommonModule, // Exporta CommonModule para que otros módulos no tengan que importarlo
FormsModule, // Exporta FormsModule para que otros módulos no tengan que importarlo
AppButtonComponent,
HighlightDirective,
// MyCustomPipe
]
})
export class SharedModule { }
<div class="callout important">🔥 <strong>Importante:</strong>
*   **No debes proporcionar servicios** en un `SharedModule` si el servicio debe ser un *singleton* global. Si un `SharedModule` es importado por múltiples módulos *lazy-loaded*, cada uno obtendrá su propia instancia del servicio. Los servicios *singleton* deben ser proporcionados en el `AppModule` o usar `providedIn: 'root'`.
*   `SharedModule` **no debe importar `BrowserModule`**, `RouterModule.forRoot()`, `HttpClientModule` (a menos que los servicios HTTP sean solo para el módulo compartido y no un *singleton* global) o cualquier otro módulo que deba ser importado solo una vez en la aplicación (`AppModule`).
</div>

3. Usar el SharedModule en otros módulos: Para usar los elementos de tu SharedModule en otro módulo (por ejemplo, FeatureModule o AppModule), simplemente impórtalo:

// feature.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../shared/shared.module'; // Ruta correcta a SharedModule
import { FeatureComponent } from './feature.component';

@NgModule({
declarations: [
FeatureComponent
],
imports: [
CommonModule,
SharedModule // Importa tu SharedModule aquí
]
})
export class FeatureModule { }

Ahora, cualquier componente declarado en FeatureModule puede usar AppButtonComponent o HighlightDirective sin tener que importarlos individualmente.

SharedModule AppButtonComponent HighlightDirective Exportados para uso común AppModule Importa SharedModule FeatureModule Importa SharedModule Import Import

📚 Bibliotecas Angular: Reutilización Entre Proyectos

Mientras que un SharedModule es excelente para compartir código dentro de una misma aplicación, las bibliotecas Angular son la solución definitiva para compartir componentes, directivas, servicios y pipes entre diferentes aplicaciones Angular o incluso para distribuirlos públicamente a través de npm.

Una biblioteca Angular es un proyecto Angular independiente que se puede construir, empaquetar y publicar. Otros proyectos pueden entonces instalar esta biblioteca como una dependencia y usar sus componentes.

¿Cuándo usar una Biblioteca en lugar de un SharedModule?

CaracterísticaSharedModule (dentro de un proyecto)Biblioteca Angular (entre proyectos)
AlcanceComparte código dentro de la misma aplicación (múltiples módulos)Comparte código entre diferentes aplicaciones (y con la comunidad)
DistribuciónParte integral del mismo build de la aplicaciónSe compila y publica como un paquete npm independiente
MantenimientoAcoplado a la aplicación principal, se actualiza con ellaVersión independiente, se actualiza y gestiona por separado
ComplejidadMás simple de implementar, no requiere configuración de build extraMás compleja de configurar y mantener, con su propio ciclo de vida
Uso idealUI kits específicos de la aplicación, utilidades internasUI kits corporativos, componentes open source, utilidades genéricas

Creando una Biblioteca Angular

Vamos a crear una biblioteca llamada ui-kit que contendrá nuestro AppButtonComponent y HighlightDirective.

  1. Crear un nuevo workspace o usar uno existente: Si ya tienes una aplicación Angular, puedes añadir la biblioteca a ese workspace. Si no, crea uno nuevo:
ng new my-workspace --no-create-application
cd my-workspace
`--no-create-application` crea solo el *workspace* sin una aplicación principal. Luego puedes añadir una aplicación o solo la biblioteca.

2. Generar la biblioteca:

ng generate library ui-kit
Esto creará una carpeta `projects/ui-kit` con una estructura de proyecto para tu biblioteca. El archivo `package.json` de la biblioteca se encuentra dentro de `projects/ui-kit`.

<div class="callout note">📌 <strong>Nota:</strong> Este comando también actualiza el archivo `angular.json` del *workspace* para incluir la configuración de la nueva biblioteca.</div>

3. Mover componentes al ui-kit: Mueve AppButtonComponent y HighlightDirective (y sus archivos asociados .ts, .html, .css) a la carpeta projects/ui-kit/src/lib. Puedes crear una subcarpeta components y directives para organizarlos.

*   **`projects/ui-kit/src/lib/components/app-button/app-button.component.ts`**
*   **`projects/ui-kit/src/lib/directives/highlight.directive.ts`**

4. Actualizar el módulo principal de la biblioteca (projects/ui-kit/src/lib/ui-kit.module.ts): Este es el módulo que los consumidores de tu biblioteca importarán. Debe declarar y exportar todos los elementos públicos de tu biblioteca.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppButtonComponent } from './components/app-button/app-button.component';
import { HighlightDirective } from './directives/highlight.directive';

@NgModule({
declarations: [
AppButtonComponent,
HighlightDirective
],
imports: [
CommonModule
],
exports: [
AppButtonComponent,
HighlightDirective
]
})
export class UiKitModule { }
  1. Configurar el archivo public-api.ts (projects/ui-kit/src/public-api.ts): Este archivo define qué partes de tu biblioteca son accesibles desde el exterior. Solo lo que se exporta aquí será importable por otras aplicaciones.
/*
* Public API Surface of ui-kit
*/
export * from './lib/ui-kit.service'; // Si tienes servicios públicos en la lib
export * from './lib/ui-kit.module';
export * from './lib/components/app-button/app-button.component';
export * from './lib/directives/highlight.directive';

Construyendo y Probando la Biblioteca

Para usar tu biblioteca, primero debes construirla.

  1. Construir la biblioteca:
ng build ui-kit
Esto compilará tu biblioteca y generará los archivos de salida en `dist/ui-kit`.

2. Probar la biblioteca localmente: Para probar tu biblioteca en una aplicación dentro del mismo workspace (o en otra aplicación local), puedes usar npm link o simplemente importar directamente desde la ruta dist.

*   **Método 1: Importación directa (para la misma *workspace*):**
    Si tienes una aplicación en el mismo *workspace* (por ejemplo, la aplicación `my-workspace` si la generaste con `ng new my-workspace`), puedes importar directamente los módulos de la biblioteca.
    En tu `app.module.ts` de la aplicación principal:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { UiKitModule } from 'ui-kit'; // Ruta de importación configurada automáticamente

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
UiKitModule // Importa tu biblioteca aquí
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
    Y luego usar los componentes en `app.component.html`:
<lib-app-button text="Botón de mi Lib" variant="primary"></lib-app-button>
<p libHighlight="orange">Texto resaltado desde la biblioteca.</p>
    Nota que el selector del componente ahora incluye el prefijo de la biblioteca (`lib-` por defecto, configurable en `angular.json`).

*   **Método 2: `npm link` (para otras aplicaciones locales o desarrollo):**
    1.  Ve a la carpeta de tu biblioteca compilada:
cd dist/ui-kit
    2.  Crea un enlace simbólico global a tu biblioteca:
npm link
    3.  En la aplicación Angular donde quieres usar la biblioteca (en su directorio raíz):
npm link ui-kit
    Esto permite a la aplicación importar `ui-kit` como si fuera un paquete npm, pero usando la versión local en `dist/ui-kit`.
💡 Consejo: Es crucial que el `package.json` de la biblioteca tenga un nombre único, especialmente si planeas publicarla en npm.

Publicando la Biblioteca en npm

Una vez que tu biblioteca está lista y probada, puedes publicarla.

  1. Asegúrate de estar logueado en npm:
npm login
Si aún no tienes una cuenta, créala en [npmjs.com](https://www.npmjs.com/).

2. Navega al directorio de salida de tu biblioteca:

cd dist/ui-kit
  1. Publica la biblioteca:
npm publish
Si es un paquete con alcance (por ejemplo, `@myorg/ui-kit`), y es un paquete público, necesitarás:
npm publish --access public
<div class="callout warning">⚠️ <strong>Advertencia:</strong> El nombre de tu paquete en `projects/ui-kit/package.json` debe ser único en npm. Si intentas publicar un nombre ya existente sin ser el propietario, fallará. Considera usar un *scope* (ej. `@tu-usuario/nombre-paquete`).</div>

Consumiendo una Biblioteca Publicada

En cualquier aplicación Angular (incluso en un proyecto completamente diferente), puedes instalar tu biblioteca:

  1. Instalar la biblioteca:
npm install ui-kit
(O `@tu-usuario/ui-kit` si usaste un *scope*)

2. Importar el módulo de la biblioteca en app.module.ts (o en cualquier otro módulo):

import { UiKitModule } from 'ui-kit'; // O '@tu-usuario/ui-kit'

@NgModule({
// ...
imports: [
// ...
UiKitModule
],
// ...
})
export class AppModule { }
  1. Usar los componentes/directivas en tus templates:
<lib-app-button text="Botón desde NPM" variant="danger" (buttonClick)="handleLibButtonClick()"></lib-app-button>
<p libHighlight="green">Este texto usa la directiva de la librería NPM.</p>
Desarrollar Biblioteca Angular ng build ui-kit npm publish npm install ui-kit (otra app) Usar componentes de la lib

🛠️ Buenas Prácticas y Consideraciones Avanzadas

  • Versiones Semánticas: Sigue el versionado semántico (Major.Minor.Patch) para tus bibliotecas. Esto ayuda a los consumidores a saber cuándo esperar cambios importantes o breaking changes.
  • Pruebas Unitarias: Asegúrate de que tus componentes, directivas y servicios de la biblioteca tengan pruebas unitarias robustas. Esto garantiza la calidad y la estabilidad a lo largo del tiempo.
  • Documentación: Documenta bien tu biblioteca, explicando cómo usar cada componente, sus entradas, salidas y ejemplos. Storybook es una excelente herramienta para esto.
  • Pequeñas y Focadas: Es mejor tener varias bibliotecas pequeñas y enfocadas que una única biblioteca monolítica con muchísimas funcionalidades.
  • Árbol Sacudible (Tree-shaking): Las bibliotecas Angular modernas son tree-shakable por defecto. Esto significa que solo el código que realmente se usa de tu biblioteca se incluirá en el bundle final de la aplicación consumidora, reduciendo el tamaño.
  • Peer Dependencies: En el package.json de tu biblioteca, usa peerDependencies para especificar las versiones de Angular y otras librerías que tu biblioteca espera que la aplicación consumidora tenga instaladas. Esto evita problemas de duplicación y versiones inconsistentes.
"peerDependencies": {
"@angular/common": ">=14.0.0",
"@angular/core": ">=14.0.0"
}
  • Nombres de Selector Únicos: Para evitar conflictos con otros componentes o bibliotecas, usa un prefijo único para los selectores de tus componentes y directivas (ej. lib-app-button, myorg-card). Esto se configura en angular.json en la sección de la biblioteca.
{ 
  "projects": {
    "ui-kit": {
      "projectType": "library",
      // ...
      "prefix": "lib" // Aquí puedes cambiar el prefijo del selector
    }
  }
}

✅ Conclusión

Dominar la creación de componentes reutilizables, la organización con SharedModule y, especialmente, el desarrollo de bibliotecas Angular, te posiciona como un desarrollador capaz de construir soluciones modulares y escalables. La reutilización no solo acelera el desarrollo, sino que también mejora la consistencia de la interfaz de usuario, reduce errores y simplifica el mantenimiento a largo plazo.

¡Anímate a crear tu propia biblioteca y compartir tus componentes favoritos con la comunidad o dentro de tu organización!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!