Angular +SheetJS: 3 formas para crear un excel
Tags: tutorial, angular, webdev, community
Published: 2022-10-19 04:19:03

En este tutorial les mostrare 3 formas para generar un Excel con la ayuda de la librería [SheetJS](https://www.npmjs.com/package/xlsx). También haré uso de la Pokemon API para trabajar con datos reales.

## Paso 1: Crear proyecto angular e instalar dependencia

Abrimos una consola CMD y ejecutamos el siguiente comando:

```
ng new simple-excel-report
```

Nos ubicamos en la carpeta del proyecto con el comando cd e instalamos la librería SheetJS ejecutando el siguiente comando:

```
npm i xlsx
```

---
## Paso 2: Integrando Pokemon API
Para este caso solo haré uso del siguiente endpoint:

```
https://pokeapi.co/api/v2/pokemon/?limit=5&offset=0
```

### Módulo para servicios compartidos
Crearemos nuestro SharedModule y en el servicio para consultar el API. Ejecutamos los siguientes comandos en la consola:
```
ng g module shared

ng g service shared/services/pokemon
```

### Importar módulo HTTP
Ahora, en el archivo **_shared.module.ts_** importamos el HttpClientModule para poder realizar peticiones http:

```
/* shared.module.ts */
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http'

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    HttpClientModule
  ]
})
export class SharedModule { }

```

### Interfaces
Dentro de la carpeta _**shared**_, crearemos una subcarpeta llamada **_interfaces_** y dentro de ella, el archivo **_pokemon-response.interface.ts_**, el cual tendrá el siguiente contenido:

```
/* pokemon-response.interface.ts */
export interface IPokemonResponse {
  count: number;
  next: string;
  previous: string;
  results: IPokemonInfo[]
}

export interface IPokemonInfo {
  name: string;
  url: string;
}

```

### Crear consulta http
Abrimos el archivo _**pokemon.service.ts**_ y en el constructor inyectamos el HttpClient de la siguiente manera:

```
constructor(private network: HttpClient) { }
```

Luego, creamos nuestro método _**getPokemonList**_ para hacer la petición GET que traerá la lista de pokemones:

```
readonly API_BASE = 'https://pokeapi.co/api/v2';

getPokemonList(offset: number, limit: number): Observable<IPokemonResponse> {
    const endpoint = `${this.API_BASE}/pokemon/?offset=${offset}&limit=${limit}`;
    return this.network.get<IPokemonResponse>(endpoint);
  }
```
Como podrán notar, agregamos 2 parámetros (offset y limit) en el método:

- **offset**: ID del pokemon inicial de la lista de respuesta
- **limit**: cantidad de pokemones subsiguientes al offset.

---
## Paso 3: Servicio para generar reporte
Desde la consola, ejecutamos el siguiente comando para generar el servicio:

```
ng g service shared/services/excel-report
```

### Forma #1: Configuración por default
Abrimos el archivo **_excel-report.service.ts_** y definimos el método generateDefaultReport:

```
generateDefaultReport(data: IPokemonInfo[], filename: string) {
    let workbook = XLSX.utils.book_new();
    let worksheet = XLSX.utils.json_to_sheet(data);

    // first way to add a sheet
    workbook.SheetNames.push('Hoja 1');
    workbook.Sheets['Hoja 1'] = worksheet;

    // second way to add a sheet
    // XLSX.utils.book_append_sheet(workbook, worksheet, "Hoja 1")
    
    XLSX.writeFileXLSX(workbook, filename, {});
  }
```
Este método recibe 2 parámetros:
- data: Arreglo de objectos de tipo IPokemonInfo.
- filename: Nombre del archivo (Ej: mi_reporte.xlsx).

El problema con esta forma es que los headers (fila A1 del Excel) son asignados automáticamente y estos serán igual al nombre de los atributos del objeto. En otras palabras, nuestros headers serán name y url (atributos de IPokemonInfo).

### Forma #2: Usando diccionario
Primero crearemos una interface con el nombre **_custom-header.interface.ts_** y con la siguiente estructura:

```
export interface ICustomHeader {
  name: string;
  key: string;
}
```
Esta interface nos ayudará a construir un diccionario relación, donde **_name_** es el nombre de la columna y **_key_** es el nombre del atributo de la interface **_IPokemonInfo_**. Ej:
- name: "Info URL"
- key: url
Teniendo esta relación definida, podemos generar un excel con headers personalizados.

A continuación, abrimos el archivo **_excel-report.service.ts_** y definimos el método generateReportWithDict:

```
generateReportWithDict(customHeaders: ICustomHeader[], data: IPokemonInfo[], filename: string) {
    let worksheetData: any[] = [];

    Object(data).forEach( (item: any) => {
      let worksheetItem = Object();
      customHeaders.forEach( header => {
        worksheetItem[header.name] = item[header.key];
      })
      worksheetData.push(worksheetItem)
    })
    
    // excel file
    let workbook = XLSX.utils.book_new();
    let worksheet = XLSX.utils.json_to_sheet(worksheetData);
    XLSX.utils.book_append_sheet(workbook, worksheet, "Hoja 1")
    XLSX.writeFileXLSX(workbook, filename, {});
  }
```
Este método recibe 3 parámetros:
- customHeaders: Arreglo de objectos ICustomHeader.
- data: Arreglo de objectos de tipo IPokemonInfo.
- filename: Nombre del archivo (Ej: mi_reporte.xlsx).

Una de las ventajas de trabajar de esta forma es que nosotros podemos definir el orden de las columnas gracias a la relación name-key.

### Forma #3: Usando un adapter
Creamos una carpeta adapter y dentro de ella, añadimos el archivo **_pokemon-report.class.ts_** con el siguiente contenido:

```
import { IPokemonInfo } from "../interfaces/pokemon-response.interface";

export interface IPokemonReport {
  pokemonId: number;
  pokemonName: string;
  pokemonUrlInfo: string;
}


export class PokemonReportAdapter {
  data: IPokemonReport[] = [];

  constructor(pokemonList: IPokemonInfo[]) {
    pokemonList.forEach( (pokemon, index) => {
      const reportItem: IPokemonReport = {
        pokemonId: index + 1,
        pokemonName: pokemon.name,
        pokemonUrlInfo: pokemon.url
      }
      this.data.push(reportItem);
    })
  }
}
```

Una ventaja de usar un adapter, es que podemos realizar operaciones y/o procesar los datos de nuestro objeto para posteriormente asignarlos en un nuevo modelo. En este caso, definimos una interface _**IPokemonReport**_ que usaremos para generar un nuevo arreglo de datos a partir de nuestra data (IPokemonInfo).

Finalmente, abrimos el archivo **_excel-report.service.ts_** y definimos el método generateReportWithAdapter:

```
generateReportWithAdapter(headers: string[], data: IPokemonReport[], filename: string) {
    let workbook = XLSX.utils.book_new();
    let worksheet = XLSX.utils.json_to_sheet([], { header: headers });

    XLSX.utils.sheet_add_json(worksheet, data, { origin: 'A2', skipHeader: true })

    XLSX.utils.book_append_sheet(workbook, worksheet, "Hoja 1")
    XLSX.writeFileXLSX(workbook, filename);
  }
```
Este método recibe 3 parámetros:
- headers: Arreglo de string, donde cada item representa el header de cada columna.
- data: Arreglo de objectos de tipo IPokemonInfo
- filename: Nombre del archivo (Ej: mi_reporte.xlsx)

Una consideración importante respecto a esta forma es el orden en que se definen el nombre de los header ya que tienen que tener el mismo orden en que se definen los atributos de la nueva interface (IPokemonReport).

---
**Espero les haya sido de utilidad este tutorial 👍.**

**Link del repositorio:** [simple-excel-report](https://github.com/JuanSW18/simple-excel-report)