AngularJS Download File From Server - Best way with Java Spring Backend

Here's how you can download a file from server using AngularJS.

In this example, the client sends a API call to Java server to download a file /api/download/{id} and server sends the base64 data stream download for a given file id.

Below is the snippet from working code. The code is pretty descriptive.
This will allow you to download any type of file.

AngularJS controller method:

function downloadReportFile(fileId) {
    Download.downloadQueuedReport({id: fileId}, function (response) {

        var anchor = angular.element('<a/>');
        anchor.attr({
            href: 'data:application/octet-stream;base64,' + response.data,
            target: '_self',
            download: response.headers.filename        });

        angular.element(document.body).append(anchor);
        anchor[0].click();

    });
}

AngularJS service to do the API call:


'downloadQueuedReport': {
    method: 'GET',
    url: 'api/download/:id',
    params: {id: '@id'},
    transformResponse: function (data, headers) {
        var response = {};
        response.data = data;
        // take note of headers() call        response.headers = headers();
        return response;
    }
},

Spring Powered Backend REST API 

@RequestMapping(value = "api/download/{id}",
    method = RequestMethod.GET,
    produces = MediaType.APPLICATION_OCTET_STREAM_VALUE
 public ResponseEntity<byte[]> downloadReportFile(@PathVariable Long id) {
    log.debug("REST request to download report file");

    File file = getReportFile(id); // a method that returns file for given ID
    if (!file.exists()) { // handle FNF
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

    try {
        FileSystemResource fileResource = new FileSystemResource(file);

        byte[] base64Bytes = Base64.encodeBase64(IOUtils.toByteArray(fileResource.getInputStream()));

        HttpHeaders headers = new HttpHeaders();
        headers.add("filename", fileResource.getFilename());

        return ResponseEntity.ok().headers(headers).body(base64Bytes);
    } catch (IOException e) {
        log.error("Failed to download file ", e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
    }

}

How browserSync actually works ?

How BrowserSync actually works ?


BrowserSync starts a small Node.js server which injects a script ( as below) into the webpage that it's monitoring.The script makes use of WebSockets to communicate between server and client to watch for changes to the code or browser actions. As soon as BrowserSync detects an action ( either in one browser or a server code) it performs a page reload.


<body>
<script id="__bs_script__">
//<![CDATA[

    document.write("<script async src='/browser-sync/browser-sync-client.2.11.2.js'> <\/script>".replace("HOST", location.hostname));
   
//]]>
</script>
...
...


If you’re already using a local web server or need to connect to a live website, you can start BrowserSync as a proxy server. See how to do this.

Articles related to BrowserSync /Grunt configuration:

BrowserSync local server proxy configuration

Integrate BrowserSync - with existing local server :

In this example, I will show how we can configure BrowserSync - Grunt task with you existing existing webapp that is running on a local server.

If you want to know the details on
  • how to configure the BrowserSync and Watch tasks  on Grunt, Please visit my previous post :

The configuration is simple : You just need to let the browserSync to know URL of your local server.

options: {
         proxy: "local.server-URL"
       }


The final Gruntfile.js file : (full configuration is already described on my earlier blog  post
BrowserSync Grunt configuration - Multi browswer Live Reload )

   
module.exports = function(grunt) {
  // Task configuration will go here
  grunt.initConfig({
   watch: {
  
      },
   browserSync: {
       bsFiles: {
         src: [
           "css/*.css", "js/.js", "./*.html" //search file/folders
         ]
       },
       options: {
         proxy: "local.server-URL" // NEEDS TO BE CONFIGURED
       }
   }
  });
  
  
  // Load tasks dependencies
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-browser-sync');
  
  // Setup default task
  // both browserSync and watch will run when running >grunt command
  grunt.registerTask('default', ['browserSync', 'watch']);

};



BrowserSync Grunt configuration - Multi browswer Live Reload

How to configure BrowserSync's Live Reload feature with Grunt tasks :


BrowserSync is capable of live reloading and syncing the changes across all your test browsers. This will launch a mini web server by using your current working directory as the base, watch your files for changes & auto-inject those changes into all connected browsers.

See my earlier blog post for the pros/cons of BrowserSync over LiveReload.

In this tutorial, I will demonstrate how to configure the BrowserSync with Grunt tasks "grunt-contrib-watch" and "grunt-browser-sync" in a basic web page setup. You can easily configure your bigger projects on you follow the following steps:



Setup Steps:

The link to github project is given at the end of this article.

1) Web App/Site setup :

I've a basic web site with the following files / directories in my working folder:

  index.html
  css/
      main.css
  js/
      app.js


BrowserSync vs LiveReload productivity boosters comparison

BrowserSync and LiveReload both are the cool tools that are aimed to improve the development workflow i.e., productivity of front-end development team. In summary, they both reload your browser automatically when you change some css or javascript or HTML - let's say any resource.

The main feature that we get from BrowserSync is : it has capacity of live reloading all your test browsers. It doesn't has constraint to one browser, meaning : it can reflect your code changes on every browser/device/emulator. The new devices/browsers can be added/tested with no additional configurations.

 

Pros and Cons of BrowserSync with LiveReload:

Pros :
  • Works across multiple devices at the same time
  • Works with all browsers and devices
    • No configuration/plugin needed in each browser
  • Synchronized actions across all browsers
    • form input, page scroll, navigation all gets synchronized across the browsers
  • Works with all browsers and devices
Con :
  • Initial configuration might be a little tricky
    • npm, grunt or gulp etc needs to be configured and the dependencies might give some problems to configure

 

Configuring Lombok on IntelliJ - Installation of Lombok plugin

How to install/configure Lombok plugin on IntelliJ IDEA :

Steps :

1) Installation 
Using IDE built-in plugin system on Windows:
  • File > Settings > Plugins > Browse repositories... > Search for "lombok" > Install Plugin
Using IDE built-in plugin system on MacOs:
  • Preferences > Settings > Plugins > Browse repositories... > Search for "lombok" > Install Plugin
Manually: 2) Restart IDE.
3) Enable Annotation Processing
 In your project: Click Preferences, "Build, Execution, Deployment", Compiler, Annotation Processors. Click Enable Annotation Processing
Enabling Annotation Processing on IntelliJ IDEA

Optional) IntelliJ and Eclipse compiler
If you're using Eclipse compiler with lombok, try this setup:
  • install plugin (above process)
  • change compiler setting:
  •  > Compiler > Java Compiler > Use Compiler: Eclipse
  •  > Compiler > Annotation Processors > Enable annotation processing: checked (default configuration)
  •  > Compiler > Additional build process VM options: -javaagent:lombok.jar

References :

MongoException$DuplicateKey: E11000 duplicate key error index - @Version

Obviously, we get this exception when we try to update/insert a document(record) with duplicate key field in MongoDB collection(table). There exists several explanation on this:

But, I was getting this error in a different scenario:

1) I already had a model User and data in it. Sample data were being created using mongeez change log file
2) Later, I added a new auditing field @Version to the entity definition.
3) When updating a User record, I got the above exception ( MongoException$DuplicateKey: E11000 duplicate key error index )

User entity :

@Document(collection = "user")
public class User implements Serializable {

    @NotNull    @Size(min = 5, max = 100)
    @Id    private String id;

    @Version    Long version; // THIS WAS ADDED LATER
    
    @Size(max = 50)
    @Field("first_name")
    private String firstName;
    @Field("last_name")
    private String lastName;

    @Email
    private String email;

 

Solution :

I added a property version and initialized to 1 in all documents


Mongo Change Log  : Using mongeez

<mongoChangeLog>
    <changeSet changeId="ChangeSet-2" author="gtiwari">
        <script>
            db.user.insert(
            {
                "_id" : "user1",
                "first_name": "",
                "last_name": "User 1",
                "email": "user1@localhost",
                "version":1  --> I UPDATED ALL user RECORDS'S VERSION = 1 TO SOLVE THIS
            });
 
 

Spring MongoDB _id field mapping conventions

Java Spring MongoDB  : '_id' field mapping :

MongoDB requires that you have an '_id' field for all documents. If you don’t provide one the driver will assign a ObjectId with a generated value. The "_id" field can be of any type the, other than arrays.

The following outlines what field will be mapped to the '_id' document field:
  • A field annotated with @Id (org.springframework.data.annotation.Id) will be mapped to the '_id' field.
  • A field without an annotation but named 'id' will be mapped to the '_id' field.
  • The default field name for identifiers is '_id' and can be customized via the @Field annotation.

Examples for the translation of '_id'-field definitions

Field definition Resulting Id-Fieldname in MongoDB
String id
_id
@Field String id
_id
@Field('x') String id
x
@Id String x
_id
@Field('x') @Id String x
_id

Source :  How the '_id' field is handled in the mapping layer

MongoDB Multiple insert at once

We can insert multiple documents (bulk insert) to a collection using a single insert command :


Syntax :
   db.[COLLECTION].insert( [ { DOC1 }, {DOC2} ] ) ;

Example :
   db.people.insert( [ {name: "Ganesh", age: 24, country : "Nepal"}, {name: "John", age : 20} ]);

In Java  ( Using Spring Data JPA Repository )

   personRepository.insert( personList);
   --> It follows org.springframework.data.mongodb.repository.MongoRepository#insert(Iterable<S> entities) method's syntax

MongoDB rename a database

As of now, there is not a straight forward way to rename a MongoDB database. But you can use the following steps to achieve so :

Here, we are trying to rename OLD_DB to NEW_DB

Steps :
1) copy old database to new one using db.CopyDatabase( OLD_DB, NEW_DB )
2) switching to OLD_DB and dropping the database

Mongo Command :

  #Step 1 : Copy database to new name
     db.copyDatabase("OLD_DB","NEW_DB") 

  #Step 2 and 3, drop OLD_DB
     use OLD_DB
     db.dropDatabase()