# Exportando datos a PDF con Angular

En esta publicación vamos a crear un servicio que permita exportar los datos de una tabla hacia un documento PDF, los cuales van a mostrarse en columnas autoajustadas.

**📦 Paquetes necesarios**

Primero lo que debemos hacer es importar los paquetes necesarios, entre los principales que se necesitarían son:

* **FileSaver**: para poder guardar archivos desde el navegador
    
* **JSPDF**: librería para generar PDF´s con JavaScript
    
* **JSPDF Autotable**: complemento de jspdf para habilitar la capacidad de generar tablas PDF ya sea analizando tablas HTML o utilizando datos JavaScript directamente
    

```typescript
import { Injectable } from '@angular/core';
import * as FileSaver from 'file-saver';
import jsPDF from 'jspdf';
import 'jspdf-autotable';
import { reduceArrayByKeys } from 'src/app/shared/helpers/utils';
import { MessagesService } from './messages.service';
```

📝 **Interfaz para cabeceras**

Lo que necesitamos luego es crear una interfaz para mantener un tipado de los datos de entrada que tendremos al momento de exportar los datos.

```typescript
interface IExportHeaders {
    header: string;
    dataKey: string;
}
```

🔍 **Función para generar cabeceras por defecto**

En caso de que no se provean las cabeceras de los datos, esta función permite obtenerlas a partir de las keys de los objetos.

```typescript
const getDefaultHeaders = (json) => {
    const defaultHeaders = [];
    const keysObj = Object.keys(json);
    for (let value of keysObj) {
        const tempObj = {
            header: value, dataKey: value
        };
        defaultHeaders.push(tempObj)
    }
    return defaultHeaders;
}
```

**⚙️ Configuración de PDF**

Luego de eso se procede a escribir la configuración del objeto de JSPDF donde se puede configurar entre otras cosas la orientación del papel.

```js
const PDFConfig = { putOnlyUsedFonts: true, orientation: 'landscape' };
```

Hecho eso ya podemos estructurar el servicio con su decorador tal cual como cualquier servicio normal de Angular, en el cual importamos un servicio para mostrar mensajes en el constructor del servicio.

```typescript
@Injectable({ providedIn: 'root' })
export class ArchivosService {

    constructor(
        private messagesService: MessagesService
    ) { }
```

Luego si especificamos la función que hará el trabajo de crear el PDF y guardarlo en el dispositivo del usuario. En este caso recibimos 4 parámetros:

* data: lo especificamos con un array de cualquier tipo ya que va a ser utilizado por múltiples componentes en nuestra aplicación
    
* headers: se recibe las cabeceras que se mostrarán en las columnas de la tabla
    
* filename: el nombre del archivo a exportarse
    
* headerTitle: el título que aparecerá
    

```typescript
exportPDF(
    data: any[],
    headers: IExportHeaders[] = [],
    filename = 'file',
    headerTitle = 'Documento eFact'
)
```

Luego verificamos si recibimos un array vacío, en ese caso retornamos un mensaje indicando que no existen datos disponibles para exportar.

```typescript
if (data.length === 0) {
    return this.messagesService.info_notification(
        'No hay datos disponibles para exportar'
    );
}
```

También verificamos que si no se enviaron las cabeceras, las obtenemos usando la función que definimos anteriormente

```typescript
if (!headers || headers.length === 0) {
    headers = getDefaultHeaders(data[0]);
}
```

Una vez que pasaron los datos las validaciones anteriores, se configura el documento jsPDF en el cual se configura el título del documento con tamaño de letra **18** y lo demás del documento con tamaño **8**.

```typescript
this.messagesService.info_notification('Generando su documento PDF...');
const doc = new jsPDF(PDFConfig);
doc.setFontSize(18);
doc.text(headerTitle, 14, 14);
doc.setFontSize(8);

const objColumns = {};
for (const header of headers) {
  objColumns[header.dataKey] = { columnWidth: 'auto' };
}
```

Luego de eso configuramos el método **autoTable** que se añade en el objeto jsPDF donde se establecen algunos parámetros como las columnas, el contenido de la tabla, la configuración de ancho de las columnas, el tema de la tabla entre otras configuraciones más.

```js
doc.autoTable({
  columns: headers,
  body: data,
  startY: 20,
  columnStyles: objColumns,
  theme: 'striped',
  tableWidth: 'auto',
  cellWidth: 'wrap',
  showHead: 'firstPage',
  headStyles: { fillColor: [52, 152, 219] },
  styles: {
    overflow: 'linebreak',
    cellWidth: 'wrap',
    fontSize: 8,
    cellPadding: 2,
    overflowColumns: 'linebreak'
  }
});
```

Para obtener el numero de paginas, se usa el método **getNumberOfPages** que provee el método internal del documento y se procede a calcular el alto de la página.

```typescript
const pageNumber = doc.internal.getNumberOfPages();
const pageSize = doc.internal.pageSize;
const pageHeight = pageSize.height || pageSize.getHeight();
```

Por último se ejecuta la función **addFooters** que se encarga de añadir el número de página en cada página del documento, el texto puede ser personalizado así como la ubicación del mismo. En el documento se establece el número de páginas y se ejecuta el método **save** del documento JSPDF.

```typescript
function addFooters() {
  for (let i = 0; i < pageNumber; i++) {
    doc.text(`Página ${i + 1} de ${pageNumber}`, 14, pageHeight - 10);
  }
}

addFooters();
doc.setPage(pageNumber);
doc.save(`${filename}`);
```

Un ejemplo de como usar este servicio seria de esta manera, importándolo en el constructor de tu componente y llamando al método del servicio.

```typescript
constructor(
    private _archivosService: ArchivosService 
){
}


print(){
    const headers = [
        {  header: 'Id', dataKey: 'id' },
        {  header: 'Nombre', dataKey: 'nombre'}
    ];
    const data = [
        { id: 1, nombre: 'Alexander Arnold' },
        { id: 2, nombre: 'Mariela Lopez' }
    ];
    this.exportPDF(data, headers, "usuarios.pdf", "Usuarios")
}
```

Listo, ya has escrito tu primer servicio que te permitirá exportar documentos PDF mandando un array de datos, el cual claramente lo puedes modificar para imprimir lo que consideres necesario.

**Ejemplo:**

## 📂 Estructura del ejemplo

* **archivos.service.ts** → Tu servicio para exportar a PDF (ya lo tienes).
    
* **usuarios.component.ts** → Componente que usa el servicio.
    
* **usuarios.component.html** → Vista con tabla y botón.
    
* **usuarios.component.css** → Estilos básicos.
    

**usuarios.component.ts**

```typescript
import { Component } from '@angular/core';
import { ArchivosService } from '../services/archivos.service';

@Component({
  selector: 'app-usuarios',
  templateUrl: './usuarios.component.html',
  styleUrls: ['./usuarios.component.css']
})
export class UsuariosComponent {

  // Datos de ejemplo
  usuarios = [
    { id: 1, nombre: 'Alexander Arnold', email: 'alex@example.com' },
    { id: 2, nombre: 'Mariela Lopez', email: 'mariela@example.com' },
    { id: 3, nombre: 'Carlos Pérez', email: 'carlos@example.com' }
  ];

  constructor(private archivosService: ArchivosService) {}

  exportarPDF() {
    const headers = [
      { header: 'ID', dataKey: 'id' },
      { header: 'Nombre', dataKey: 'nombre' },
      { header: 'Email', dataKey: 'email' }
    ];

    this.archivosService.exportPDF(
      this.usuarios,
      headers,
      'usuarios.pdf',
      'Lista de Usuarios'
    );
  }
}
```

**usuarios.component.html**

```xml
<div class="container">
  <h2>Lista de Usuarios</h2>

  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>Nombre</th>
        <th>Email</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let usuario of usuarios">
        <td>{{ usuario.id }}</td>
        <td>{{ usuario.nombre }}</td>
        <td>{{ usuario.email }}</td>
      </tr>
    </tbody>
  </table>

  <button (click)="exportarPDF()">📄 Exportar a PDF</button>
</div>
```

**usuarios.component.css**

```css
.container {
  max-width: 600px;
  margin: auto;
  text-align: center;
  font-family: Arial, sans-serif;
}

h2 {
  margin-bottom: 20px;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 15px;
}

table, th, td {
  border: 1px solid #ccc;
}

th {
  background-color: #3498db;
  color: white;
  padding: 8px;
}

td {
  padding: 8px;
}

button {
  background-color: #2ecc71;
  color: white;
  border: none;
  padding: 10px 15px;
  cursor: pointer;
  border-radius: 4px;
}

button:hover {
  background-color: #27ae60;
}
```

## 🔹 Cómo funciona

1. El usuario ve la tabla con los datos.
    
2. Al hacer clic en **"Exportar a PDF"**, se llama al método `exportarPDF()` del componente.
    
3. Este método prepara las cabeceras y llama al servicio `ArchivosService`.
    
4. El servicio genera el PDF con `jsPDF` y `autoTable` y lo descarga automáticamente.
