Servir un archivo para descargar en Angular

Oluwafisayo Oluwatayo 20 marzo 2023
  1. Servir un archivo .txt para descargar usando Angular
  2. Servir archivo para descargar con ngx-filesaver
  3. Servir un archivo .jpg para descargar usando Angular
  4. Conclusión
Servir un archivo para descargar en Angular

La felicidad ignorante es cuando buscas descargar un archivo, inicias sesión en un sitio, buscas el archivo, lo encuentras, haces clic en descargar y terminas sin conocer las tareas y los esfuerzos detrás de servir esos archivos para descargar en ese sitio. Para los desarrolladores web, servir un archivo para descargar puede ser una tarea tal que se encontrará envidiando a estos usuarios inocentes.

Pero, ¿cómo servimos archivos para descargar usando el marco Angular? Consideremos algunos enfoques.

Servir un archivo .txt para descargar usando Angular

El primer enfoque aquí es convertir un texto en formato .txt y servirlo para su descarga. Para servir archivos para descargar, necesitamos utilizar blob, que es un almacenamiento virtual para entregar documentos directamente a un navegador.

Lo primero que debe hacer es comenzar un nuevo proyecto Angular. Luego, dentro del archivo app.component.html, escribiremos estos códigos:

Fragmento de código- app.component.html:

<a [href]="fileUrl" download="file.txt">DownloadFile</a>

Esto crea la estructura de la página web y el enlace en el que se hará clic para descargar.

Luego necesitamos crear una función y un comportamiento para la aplicación. Lo haremos dentro del archivo app.component.ts, como se muestra a continuación:

Fragmento de código- app.component.ts:

import { Component } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'done';
  fileUrl;
  constructor(private sanitizer: DomSanitizer) {  }
  ngOnInit() {
    const data = 'some text';
    const blob = new Blob([data], { type: 'application/octet-stream' });
    this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
  }
}

Comenzamos definiendo los datos que deseamos guardar; es un texto, some text. Luego definimos la función Blob y declaramos los datos que queremos guardar dentro de la función.

Usaremos el módulo DomSanitizer para permitir que el archivo que servimos para descargar pase la prueba de seguridad de Angular. Luego vamos al archivo app.module.ts para importar los módulos que hemos usado para crear la app.

Fragmento de código- app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Producción:

Archivo de texto angular para descargar

Servir archivo para descargar con ngx-filesaver

Veamos ahora cómo podemos servir un archivo para descargar usando la dependencia ngx en Angular. El ngx-filesaver es un paquete seguro y popular que ofrece una forma sencilla de servir archivos para descargar.

El ngx-filesaver se puede instalar escribiendo npm i ngx-filesaver en el directorio de la carpeta del proyecto recién creado.

Para continuar, navegaremos hasta el archivo app.component.html y escribiremos estos códigos para crear la estructura de la aplicación web.

Fragmento de código- app.component.html:

<h1></h1>
<textarea [(ngModel)]="text"></textarea>
<button type="button" (click)="onDown()">Save Text</button>

Aquí, hemos creado un botón con el detector de eventos onDown para que cuando se haga clic en el botón, se descargue el archivo que servimos.

Ahora debemos hacer el trabajo real dentro del archivo app.component.ts, como se muestra a continuación:

Fragmento de código- app.component.ts:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FileSaverService } from 'ngx-filesaver';

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

export class AppComponent implements OnInit{
  title = 'loginfive';
  public text = 'hello';
  constructor(
    private _httpClient: HttpClient,
    private _FileSaverService: FileSaverService
  ) {}

  ngOnInit() {}

  onDown() {
    const fileName = `save.txt`;
    const fileType = this._FileSaverService.genType(fileName);
    const txtBlob = new Blob([this.text], { type: fileType });
    this._FileSaverService.save(txtBlob, fileName);
    }
}

Observará que en el archivo app.component.ts, indicamos el detector de eventos onDown dentro de la etiqueta button. Aquí, definimos las funciones para ello.

Primero, declaramos los datos que se servirán para su descarga; es un archivo de texto. Luego utilizamos blob para servir el archivo para su descarga en el navegador.

La última parte de este enfoque es importar los módulos que usamos para desarrollar la aplicación web.

Fragmento de código- app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { FileSaverModule } from 'ngx-filesaver';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule,
    FormsModule,
    HttpClientModule,
    FileSaverModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

Producción:

Archivo angular para descargar usando ngx-filesaver

Servir un archivo .jpg para descargar usando Angular

Hasta ahora, hemos estado analizando cómo publicar archivos .txt para su descarga. Ahora veamos cómo podemos servir una imagen para descargar.

Obtendremos la imagen de una URL y la descarga mostrará una barra de progreso cortesía de Angular Material.

Primero debemos crear un nuevo proyecto Angular e instalar el paquete Angular Material, navegar a la carpeta del proyecto y escribir esto: ng add @*angular/material.

Después de una instalación exitosa, pasaremos al archivo app.component.html para crear la estructura de la página para incluir la diapositiva de progreso de la descarga.

Fragmento de código- app.component.html:

<mat-card>
    <div class="file">
        <p>{{ slides.name }}</p>
        <button (click)="download(slides)" mat-raised-button color="primary">Download</button>
    </div>
    <mat-progress-bar *ngIf="download$ | async as download"
        [mode]="download.state == 'PENDING' ? 'buffer' : 'determinate'"
    [value]="download.progress">
    </mat-progress-bar>
</mat-card>

Necesitamos crear un componente donde trabajaremos en códigos para observar el progreso de la descarga, activando la diapositiva de material. Crearemos un archivo, lo llamaremos download.ts, y trabajaremos estos códigos dentro.

Fragmento de código- download.ts:

import {
    HttpEvent,
    HttpEventType,
    HttpProgressEvent,
    HttpResponse
  } from "@angular/common/http";
  import { Observable } from "rxjs";
  import { distinctUntilChanged, scan, map, tap } from "rxjs/operators";

  function isHttpResponse<T>(event: HttpEvent<T>): event is HttpResponse<T> {
    return event.type === HttpEventType.Response;
  }

  function isHttpProgressEvent(
    event: HttpEvent<unknown>
  ): event is HttpProgressEvent {
    return (
      event.type === HttpEventType.DownloadProgress ||
      event.type === HttpEventType.UploadProgress
    );
  }

  export interface Download {
    content: Blob | any;
    progress: number;
    state: "PENDING" | "IN_PROGRESS" | "DONE";
  }

  export function download(
    saver?: (b: Blob) => void
  ): (source: Observable<HttpEvent<Blob>>) => Observable<Download> {
    return (source: Observable<HttpEvent<Blob>>) =>
      source.pipe(
        scan(
          (download: Download, event): Download => {
            if (isHttpProgressEvent(event)) {
              return {
                progress: event.total
                  ? Math.round((100 * event.loaded) / event.total)
                  : download.progress,
                state: "IN_PROGRESS",
                content: null
              };
            }
            if (isHttpResponse(event)) {
              if (saver) {
                saver(event.body);
              }
              return {
                progress: 100,
                state: "DONE",
                content: event.body
              };
            }
            return download;
          },
          { state: "PENDING", progress: 0, content: null }
        ),
        distinctUntilChanged((a, b) => a.state === b.state
          && a.progress === b.progress
          && a.content === b.content
        )
      );
  }

Dado que necesitamos observar el progreso de descarga del archivo que servimos para descargar, usamos la función Observable para monitorear el progreso de descarga y luego traducirlo al control deslizante de material. Lo siguiente es crear un archivo de servicio que manejará la obtención del archivo desde la URL y luego lo mostrará con la diapositiva de progreso.

Vamos a crear un archivo de servicio, asígnele el nombre download.service.ts y escriba estos códigos:

Fragmento de código- download.service.ts:

import { Injectable, Inject } from '@angular/core'
import { HttpClient, HttpRequest } from '@angular/common/http'
import { download, Download } from './download'
import { map } from 'rxjs/operators'
import { Observable } from 'rxjs'
import { SAVER, Saver } from './saver.provider'

@Injectable({providedIn: 'root'})
export class DownloadService {

  constructor(
    private http: HttpClient,
    @Inject(SAVER) private save: Saver
  ) {
  }

  download(url: string, filename?: string): Observable<Download> {
    return this.http.get(url, {
      reportProgress: true,
      observe: 'events',
      responseType: 'blob'
    }).pipe(download(blob => this.save(blob, filename)))
  }

  blob(url: string, filename?: string): Observable<Blob> {
    return this.http.get(url, {
      responseType: 'blob'
    })
  }
}

Para proporcionar estos servicios a nuestra aplicación, necesitamos un proveedor, creamos un nuevo archivo, lo llamamos saver.provider.ts y le ponemos los códigos.

Fragmento de código- saver.provider.ts:

import {InjectionToken} from '@angular/core'
import { saveAs } from 'file-saver';

export type Saver = (blob: Blob, filename?: string) => void
export const SAVER = new InjectionToken<Saver>('saver')
export function getSaver(): Saver {
  return saveAs;
}

Lo siguiente es obtener la URL de la imagen que queremos servir para descargar. Lo haremos dentro del archivo app.component.ts y escribiremos estos códigos.

Fragmento de código- app.component.ts:

import { Component, Inject } from '@angular/core';
import { DownloadService } from './download.service'
import { Download } from './download'
import { Observable } from 'rxjs'
import { tap } from 'rxjs/operators'
import { DOCUMENT } from '@angular/common'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'done';
  slides =
    {name: 'Click here:', url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg'}
  download$: Observable<Download>

  constructor(private downloads: DownloadService,
              @Inject(DOCUMENT) private document: Document) {}

  download({name, url}: {name: string, url: string}) {
    this.download$ = this.downloads.download(url, name)
  }
}

Necesitamos toneladas de módulos para que la aplicación funcione. Necesitamos importar estos módulos dentro del archivo app.module.ts.

Fragmento de código- app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'
import {MatButtonModule} from '@angular/material/button';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatCardModule} from '@angular/material/card';
import { BrowserAnimationsModule} from '@angular/platform-browser/animations'
import { AppComponent } from './app.component';
import { SAVER, getSaver } from './saver.provider'

@NgModule({
  imports:      [
    BrowserModule,
    FormsModule,
    BrowserAnimationsModule,
    HttpClientModule,
    MatButtonModule,
    MatProgressBarModule,
    MatCardModule
  ],
  providers: [{provide: SAVER, useFactory: getSaver}],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

Luego necesitamos embellecer la página con algunos estilos. Agregaremos este pequeño fragmento dentro del archivo app.component.css.

Fragmento de código- app.component.css:

mat-card {
    display: flex;
    flex-direction: column;
  }

  .file {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

Producción:

Archivo jpg angular para descargar

Conclusión

Dependiendo del archivo que queramos servir para descargar, hay varios enfoques que podemos adoptar para implementar esto dentro de nuestra aplicación Angular, usando Angular blob para guardar los archivos.

Oluwafisayo Oluwatayo avatar Oluwafisayo Oluwatayo avatar

Fisayo is a tech expert and enthusiast who loves to solve problems, seek new challenges and aim to spread the knowledge of what she has learned across the globe.

LinkedIn

Artículo relacionado - Angular Download