How to Retrieve a Remote File in Angular using $http.get() with OAuth Authentication

I have a unique situation where my users possess private files that require downloads by authenticated users. The server I am using initially downloads a file from S3 utilizing its own set of S3 app_id and secret_token credentials. Once the file has been downloaded, it is then compiled and transmitted to the client using Rails' send_data method.

Here's how it looks in Ruby on Rails:

# documents_controller.rb
def download
  some_file = SomeFile.find(params[:id])

  # download file from AWS S3 to server
  data = open(some_file.document.url) 

  # construct and send downloaded file to client
  send_data data.read, filename: some_file.document_identifier, disposition: 'inline', stream: 'true'
end

Initially, my plan was to trigger the download directly from the HTML template as shown below:

HTML:

<!-- download-template.html -->
<a target="_self" ng-href="{{ document.download_url }}" download="{{document.file_name}}">Download</a>

Although this seemed straightforward, I encountered an issue with Angular's $http interceptor not intercepting external link clicks, resulting in missing headers needed for server-side authentication. This led to a 401 Unauthorized Error.

To overcome this challenge, I decided to trigger the download using ng-click and initiating an $http.get() request from the angular controller instead.

HTML:

<!-- download-template.html -->
<div ng-controller="DocumentCtrl">
  <a ng-click="download(document)">Download</a>
</div>

Javascript:

// DocumentCtrl.js
module.controller( "DocumentCtrl",
  [ "$http", "$scope", "FileSaver", "Blob",
  function( $http, $scope, FileSaver, Blob ) {

    $scope.download = function( document ) {
      $http.get(document.download_url, {}, { responseType: "arraybuffer" } )
        .success( function( data ) {
          var blob = new Blob([data], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
          FileSaver.saveAs(blob, document.file_name);
        });
    };
}]);

FileSaver is a useful library for saving files using Blobs on the client side.

This approach helped me resolve the authentication problem, but unfortunately, the downloaded file ended up being saved/downloaded in an unreadable or unusable format.

What could be causing the file to download in an unusable format?

Thank you in advance.

Answer №1

To successfully retrieve binary data as a response in Angular, it is necessary to configure the $http method accordingly.

Referencing Rails' send_data documentation:

The send_data method allows for sending binary data to the browser. It provides options for displaying the response as either an attachment or inline content, specifying content type, file name, and more.

In Angular's $http documentation, there is limited information available on configuring responseType for handling binary data responses. Essentially, responseType needs to be set to "arraybuffer" to indicate that the response will contain binary data (as shown below).

$scope.download = function(document) {
  console.log("download: ", document);
  $http({
    url: document.download_url,
    method: "GET",
    headers: {
      "Content-type": "application/json"
    },
    responseType: "arraybuffer" // expect to handle binary data response
  }).success(function(data, status, headers) {
      var type = headers('Content-Type');
      var blob = new Blob([data], {type: type});
      FileSaver.saveAs(blob, document.file_name);
    });
};

It would be beneficial for Angular's $http documentation to provide more detailed explanations rather than just listing the basic usage and arguments.

Usage

$http(config);

Arguments

config

responseType - {string} - see XMLHttpRequest.responseType.

Answer №2

Hey there! I wanted to share an example of how I handle file downloads from my server using Angular:

First, I make a GET request to retrieve the file:

Here is the HTML code for initiating the file download on the client side:

<a ng-href="/api/downloadFile/{{download.id}}" type="submit" class="btn btn-primary col-lg-12 btn-modal-costume" >Download</a>

And here is the Java code to handle the file download on the server side:

public static Result download(String id) {
        String content = null;
        for (controllers.file file : files) {
            if (file.getId().equals(id)){
                content = file.getContent();
            }
        }
        return ok(new java.io.File("/temp/" + id+ "file" + content)).as("application/force-download");
    }

If you're interested, you can access the full code in my GitHub project

Answer №3

It seems like you were heading in the right direction with your approach using JavaScript, but there was a small mistake. Instead of passing an empty object as the second parameter in the $http.get call, you should have included the options argument with {responseType: arraybuffer}. For more information, refer to the documentation for $http.get available at: https://docs.angularjs.org/api/ng/service/$http#get

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

Instructions for activating and deactivating a numerical input field with a checkbox

Is there a way to create a pair of a checkbox and number field that are linked together? When the checkbox is clicked, it should disable the associated number field. Javascript File: $(document).ready(function(){ $("input[name=check]").click(function(){ ...

Adding static files to your HTML page with node.js

This is not a question about using express.static() In my application, I have multiple pages that share the same JS and CSS dependencies. Instead of adding <script> or <link> tags to every single page, I'm looking for a way to include the ...

Switching the active className in React and Next.js based on selection status

My goal is to dynamically change the className of each Card component when clicked on. When one Card is selected, the others should revert back to their default className. This is my UserBookingData component: const UserBookingData = ({ bookings }: any) = ...

Guidance on Implementing Promises in Ionic 2 and Angular 2

Here are two functions that I need to implement: this.fetchQuizStorage(); this.retrieveQuizData(); fetchQuizStorage() { this.quizStorage.getAnswers().then(data => { return data; }); } retrieveQuizData() { this.quizData.getQuiz().t ...

Choose to either check or uncheck boxes using ReactJS

After successfully creating a function to select either single or multiple boxes, I encountered an issue with the "Select all" feature. Any suggestions on how to resolve this? (I'm utilizing the material-ui library for my checkboxes, which are essenti ...

No information available at the moment

When the data is not present, it displays as "display none"... However, I want it to show "no data found" This is the current code if (a.innerHTML.toUpperCase().indexOf(filter) > -1) { li[i].style.display = ""; } else { li[i].styl ...

What could be the reason behind Cesium viewer's failure to show a model after I upload it?

My application features a 3D map of a city with an "Add building" button. Upon clicking this button, a model of a building should be inserted into the map. However, all I see is a selection marker at the intended location without the actual building appea ...

Reorganizing Arrays in Javascript: A How-To Guide

I have an array in JavaScript called var rows. [ { email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="81f4f2e4f3b0c1e4f9e0ecf1ede4afe2eeec">[email protected]</a>' }, { email: '<a hre ...

Having difficulty retrieving the value of a variable obtained from the Google Distance Matrix function

Utilizing the Google distance matrix API to calculate the distance between two locations, I encountered an issue with global variable access. Despite changing the variable within a function, I found that I was unable to retrieve the updated value of the va ...

Ways to retrieve the chosen option in a dropdown list without specifying the dropdown's name, id,

Custom dropdown, Model-View-Controller Code @foreach (var attribute in Model) { string controlId = string.Format("product_attribute_{0}_{1}_{2}", attribute.ProductId, attribute.ProductAttributeId, attribute.Id); @switch (attribute.AttributeControl ...

Linking asynchronous functions does not function properly

I've been attempting to link two asynchronous functions together, but it appears that the second function is running before the first one. Below is the code snippet: function performAction(e) { const ZIP = document.getElementById('zip').valu ...

Unable to invoke JS function in Drupal 5 file

In my current project using Drupal 5, I have a specific .js file that is included using the code: drupal_add_js(drupal_get_path('module','MTM')."/include/JS_form.js"); Additionally, there is an element on the page: <a onclick="MTM ...

It seems like KineticJS is removing elements from the canvas that I would prefer to keep

My website features an HTML5 canvas where I showcase a variety of images, text, and shapes using JavaScript functions. The text and shapes are created with the following JavaScript functions: function drawGameElements(){ /* Draw a line for the ' ...

Having trouble utilizing a function with an async onload method within a service in Angular - why does the same function work flawlessly in a component?

I successfully created a component in Angular that can import an Excel file, convert it into an array, and display its content as a table on the page. The current implementation within the component looks like this: data-import.compoent.ts import { Compo ...

A concise way to write an else if statement in Javascript and jQuery

Is there a way to make this code more concise? It works perfectly fine, but it's too lengthy. Basically, the code involves two dropdown lists where the user selects options, and based on their selection, values appear in two textboxes. The catch is th ...

Establishing communication between a master process and worker processes in Node.js: A guide to verifying bidirectional communication

After coming across this particular script from the node documentation, I tried to implement it for sending messages between Master and worker processes using cluster. However, upon running the script, I encountered an issue where I could not verify the me ...

Encountered a problem while establishing a connection to Firebase following the upgrade of

Ever since I upgraded angularfire to version 2.0.1 in order to support the latest Firebase version 3.2.0, my app has been unable to establish a connection with the database. I'm not sure what could be causing this issue. Here's the code that used ...

How can I display an array of data with a changing name using a FlatList in React Native?

How can I render a list of array data with a dynamic name in a FlatList using React Native? Below is the list of data that I would like to display in the FlatList: const movies = [ { '4W2JJ0CLbvfLJzBUHORVaz6sAGv2': [ { name: ...

A step-by-step guide to adding a checkbox column dynamically within handsontable

I am currently utilizing handsontable within a jsfiddle at http://jsfiddle.net/kc11/cb920ear/1/. My task involves dynamically inserting a checkbox column before the existing data. The structure I am working with appears to be a multidimensional array, as s ...

What is the most popular method for namespacing AngularJS modules?

I am new to AngularJS and currently exploring different ways to namespace modules in my application. One challenge I face is the need to integrate my Angular app into a designated placeholder div within a third-party website (which may also use Angular), ...