Working with Angular 5/6 to Manage File Downloads with Customized File Names via ASP.NET Core API Calls

My ASP.NET Core 2.1 API endpoint allows users to download a CSV file by hitting the "Gimme" route:

[HttpPost("Gimme")]
public IActionResult Gimme([FromBody] MyOptions options)
{
    MemoryStream reportStream = _reportGenerator.GenerateReportStream(options.StartDate, options.EndDate);

    return new FileStreamResult(reportStream, "text/csv") { FileDownloadName = "report.csv" };
}

Testing this endpoint through POSTMan shows that it returns the CSV file with correct data without any issues.

Now enters Angular. The web application we're using to interact with our API is an Angular-driven Single Page Application. Upon researching how to handle files coming from API endpoints in Angular, I found various methods involving creating Blobs and inline URLs for navigation using JavaScript functions like window.open() or manipulating a tags in-memory.

Here's where my question arises: Does the latest version of Angular not offer a built-in solution for handling this scenario? As someone who is not an expert in Angular, I expected there to be documentation or native mechanisms on Angular's website for serving downloaded files to browsers. It appears that current solutions tend to involve workarounds rather than direct support.

While my existing method successfully downloads the file with the specified name from the API, it saves as a temporary file with a GUID-based name instead:

https://i.sstatic.net/H4g2b.png

Included below are snippets from both my Component and Service classes. I've updated the downloadFile() method based on a solution suggested in this Stack Overflow answer to address filename display concerns, but I'm still searching for a more elegant solution.

// Component

DownloadReport(options: ReportOptions) {
    this._service.getCSV(options).subscribe(data => this.downloadFile(data));
}

downloadFile(blob: Blob) {
    const fileName = 'report.csv';
    if (navigator.msSaveBlob) {
        // IE 10+
        navigator.msSaveBlob(blob, fileName);
    } else {
        const link = document.createElement('a');
        const url = URL.createObjectURL(blob);
        link.setAttribute('href', url);
        link.setAttribute('download', fileName);
        link.style.visibility = 'hidden';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
    }
}

// Service

getCSV(reportOptions: ReportOptions): Observable<any> {
    let headers = new HttpHeaders();
    headers = headers.set('Accept', 'text/csv');
    return this._httpClient
        .post(`${this.apiRoot}/Gimme`, reportOptions, { headers: headers, responseType: 'blob' })
        .catch(this.handleError);
}

Presently, I am resorting to the createObjectURL workaround to achieve functionality (borrowed from random online sources). Is there a recommended "Best Practices" approach for this issue?

Answer №1

This is my preferred method. It's essentially similar to what you're already using, but I find it looks a bit cleaner with the Anchor typing feature. I spent quite some time researching the best way to achieve this and couldn't come across a more elegant solution.

  download(): void {
    this.service.getFile({ id: id, name: name })
      .subscribe(data => {
        if (window.navigator && window.navigator.msSaveOrOpenBlob) {
          //saving file for IE
          window.navigator.msSaveOrOpenBlob(data, name);
        } else {
          const objectUrl: string = URL.createObjectURL(data);
          const a: HTMLAnchorElement = document.createElement('a') as HTMLAnchorElement;

          a.href = objectUrl;
          a.download = name;
          document.body.appendChild(a);
          a.click();

          document.body.removeChild(a);
          URL.revokeObjectURL(objectUrl);
        }
      });
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ways to designate the preceding page for the web browser's back button

I'm currently in the process of developing a web application. Upon logging in, users are directed to a landing page as their initial point of entry. From this landing page, they have the option to navigate to a more detailed page by clicking on a sp ...

Is it possible to create a Three.js animation with a see-through background?

I have an HTML5 video with a greenscreen effect that I want to display on a plane using a video texture in Three.js. I attempted to use separate video textures for the main video and the alpha channel, but encountered synchronization issues. From what I u ...

Guide to updating user profile information using mongoose

When a user updates their profile on the client side using a form, the data is sent through axios to the nodejs express backend. I am looking to implement the mongoose library for updating user information. This is the approach I have taken so far: userR ...

Tips for using jQuery to verify the most recent entry in a form

Struggling with this issue - I can't seem to get jquery to function the way I need it to: Essentially, when a user exits a text field, jquery is supposed to capture the input value and respond accordingly: for example, if the user types 'value& ...

Angular input field with the ability to add and remove multiple options

Users can enter multiple emails to be added to a list, and they have the option to remove emails from the list. https://i.sstatic.net/vnHpJ.png ...

employing flush for lodash's throttle wrapper

When using TypeScript with JavaScript and Angular: I am trying to use the throttle decorator from lodash to limit an API call while a user is navigating around the page, ensuring that it fires before they leave the site. In my TypeScript constructor, I h ...

Error encountered in Selenium Webdriver: Element is not currently in view

I encountered an issue when trying to click on a button using the same type of code that worked fine for selecting a drop down. The error message I received was "Element is not currently visible and so may not be interacted with". Command duration or timeo ...

Encountering a problem with ng-repeat when working with a JSON

Having a bit of trouble displaying the keys and values from a JSON object in an HTML table using ng-repeat. The JSON object is coming from the backend, but for some reason, it's not showing up on the frontend. I know there must be a simple mistake som ...

Lending a hand in deselecting a rule within the jcf-hidden add-on using Selenium

Currently, I am working on automating a website that utilizes the jcf-hidden class (a form of jQuery class) which hides the select element I want to access. By removing the display: block !important; property, I was able to successfully interact with the ...

Reading a Json file with keys in puppeteer BDD: A Guide

Greetings, I am new to the world of puppeteer. I have successfully created my basic framework and now I am trying to figure out how to read data from .json files. I attempted to use the readFile method in my helper class, but unfortunately, it resulted in ...

Iterating through an array in Angular with ng-repeat and dynamically adding two elements to a div instead of just one

Utilizing Angular, I have set up a get request to retrieve live data. My goal is to then showcase this data on the home page. Here is the code snippet from my controller: $scope.holder = []; $http.get('url').success(function(data){ $sc ...

What could be causing my canvas to not display my sprite?

Currently, I am in the process of learning JavaScript and experimenting with creating games to make the learning experience more enjoyable. However, I am facing an issue while using EaselJS and CreateJS to develop these games as a beginner. I need some as ...

How can we nest a div within the outermost parent element?

Consider a scenario where there are 3 different divs named grandParent, Parent, and Child. Although the parent's tag is placed inside the grandParent, due to the use of position: absolute; property, the parent ends up being displayed outside the grand ...

What steps should I take to resolve the error message stating: "Type Form1 already defines a member called Dispose with the same parameter types"?

Attempting to modify the names of the methods and also attempted to create a new class to transfer the code from form1, however, nothing yielded results. This is the code within form1: using System; using System.Collections.Generic; using System.Componen ...

Tips for enhancing the appearance of a React component

I have a redux form that doesn't look great, and I would like to style it. However, my project uses modular CSS loaders. The styling currently looks like this: import styled from 'styled-components'; const Input = styled.input` color: #4 ...

Can the AJAX URL be set as the function itself?

I'm still quite new to AJAX and recently came across a code snippet where the URL was set to the function itself, in this case "getPath". Typically, we would set the URL to other pages to retrieve data or perform other actions. I'm puzzled by wha ...

Occasionally, the translate.get function may return the key instead of the intended

I recently encountered an issue while working on a web application built with Angular 12 and ASP.NET Core 5. The application utilizes ngx-translate for internationalization, but I faced a problem with translating content in a new component that I added to ...

VueJS 2 - Monitoring variables within arrays and objects

Illustration of the issue: https://jsfiddle.net/hxv5bLqt/ In my data structure, there are two levels (arrays). The top level represents "parts", and each "part" contains a set of "items". Additionally, there is a variable that stores the total value of al ...

The animation of the splash screen in Angular is quite jarring and lacks fluidity

I am experiencing some issues with my angular splash screen animation. It works perfectly when there is no activity in the background, but when I simulate a real-life application scenario, the animation becomes stuttered, choppy, or sometimes does not anim ...

Verify the occurrence of a search result and if it appears more than once, only show it once using JavaScript

Hello all. Currently, I am developing an online users script using NodeJS and SocketIO. The functionality works fine, however, I am encountering an issue where if a user connects from multiple browsers, windows, or devices, it displays duplicate results li ...