File Upload from Browser


#1

Hi All,

I’m trying to upload a file from the browser with ng2-file-upload and without Cordova.

The node/express server works fine when I test in Postman. However, it’s not working in Ionic. Oddly, it doesn’t say it fails in Chrome’s Network tab.

The app.js file is:

// npm i -g multer
// npm i -g nodemon

var express = require('express'),
multer      = require('multer'),
fs          = require('fs'),
app         = express(),
http        = require("http"),
multer      = require('multer'),
server      = http.createServer(app),
router      = express.Router(),
path        = require('path');

app.use(function (req, res, next) {
                res.header("Access-Control-Allow-Methods", "POST, PUT, OPTIONS, DELETE, GET");
                res.header('Access-Control-Allow-Origin', 'http://localhost:3001');
                res.header('Access-Control-Allow-Origin', 'http://localhost:8100');
                res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
                //res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Credentials", true);
                next();
});

var middleware = [
                express.static(path.join(__dirname, './uploads/'))
]

app.use(middleware);

var storage = multer.diskStorage({
                destination: function (req, file, callback) {
                                callback(null, './uploads/')
                },

                filename: function (req, file, callback) {
                                var datetimestamp = Date.now();
                                console.log("Filename " + file.originalname);
                                callback(null, file.originalname);
                },

                onFileUploadStart: function (file) {
                             console.log(file.originalname + ' is starting ...');
                },
                onFileUploadComplete: function (file) {
                             console.log(file.fieldname + ' uploaded to  ' + file.path);
                }
});

var upload = multer().any('selectedFile')
//**THIS IS MANDATORY, WITHOUT THIS NOT WORK**

app.use(multer({
                storage:storage
}).any('selectedFile'));

app.get('/', (req, res) => res.end('Hello root!!'))

app.get('/api', function (req, res) {
                res.end('You're using a GET, not a POST');
});

//app.use('/api', router);
app.post('/api', function (req, res, next) {
  upload(req,res,function(err){
    if(err){
      res.status(500).json({'success':false});
      return;
    }

    res.status(200).json({success:true,message:"File was uploaded successfully !"});
  });
});

var PORT = process.env.PORT || 3001;

app.listen(PORT, function () {
  console.log('Working on port ' + PORT);
});

To test in Postman,

New > Get > (change “GET” dropdown to “POST” > path is “http://localhost:3001/api”,

The set the Params > Body > Key = “selectedFile”.

Next to Key, set the “Text” dropdown to “File” and select any test file.

Press Send.

Here’s the Ionic code:

home.ts

import { Component } from '@angular/core';
import { FileUploader } from 'ng2-file-upload';

const URL = 'http://localhost:3001/api/';

@Component({
    selector: 'home-page',
    templateUrl: './home.html'
})

export class HomePage {
    
    uploader:FileUploader;
    hasBaseDropZoneOver:boolean;
    hasAnotherDropZoneOver:boolean;
    response:string;
 
    constructor() {
        this.uploader = new FileUploader({
            url: URL,
            disableMultipart: true, // 'DisableMultipart' must be 'true' for formatDataFunction to be called.
            formatDataFunctionIsAsync: true,
            formatDataFunction: async (item) => {
                return new Promise( (resolve, reject) => {
                    resolve({
                        name: 'selectedFile',
                        filename: item._file.name,
                        contentType: item._file.type,
                        /*
                        name: item._file.name,
                        length: item._file.size,
                        contentType: item._file.type,
                        date : item._file.lastModifiedDate
                        */
                    });
                });
            }
        });
        
        this.hasBaseDropZoneOver = false;
        this.hasAnotherDropZoneOver = false;
        this.response = '';
        this.uploader.response.subscribe( res => this.response = res );
    }   
    
    public fileOverBase(e:any):void {
        this.hasBaseDropZoneOver = e;
    }
    
    public fileOverAnother(e:any):void {
        this.hasAnotherDropZoneOver = e;
    }
    
    public showFunction(): void {
        var x = document.getElementById("hiddenDiv");
        
        if (x.style.display === "none")
            x.style.display = "block";
        else
            x.style.display = "none";
    }
 
    public DisplaySize(item:any): string {
        let b: number = 0;

        if (item && item.file)
            b = item.file.size;

        if (b < 1024)
            return b + " bytes";
        
        b /= 1024;                              // convert to KB
        if (b < 1024)
            return b.toFixed(2) + " KB";

        b /= 1024;                              // convert to MB
        return b.toFixed(2) + " MB";
    }
}

home.html

<ion-content style="background-color: #842024" >
    <div class="container">
        <div class="row">
            <div class="col-md-3">
                <div ng2FileDrop
                     [ngClass]="{'nv-file-over': hasBaseDropZoneOver}"
                     (fileOver)="fileOverBase($event)"
                     [uploader]="uploader"
                     class="well my-drop-zone">
                    <h5 class="drag-label"> Drag File(s) Here To Upload </h5>
                </div>  
                
                <br/>
                
                <form action='#' onsubmit="return false;">
                    <input type="file" id="filename" name="selectedFile" ng2FileSelect [uploader]="uploader" multiple/><br/>
                </form>
            </div>
        </div>
    </div>
    
    <hr />
    <div class="container" id="containerdiv">
        <div class="col-md-9" id="resultsdiv">
            <div class="results">
                <p style="font-size:15px; text-align:center; color: #842024; text-transform: uppercase"> Staging Area </p>      
                <p> File Queue: {{ uploader?.queue?.length }} </p><br/>
                
                <div class="progressdiv">
                    <button type="button" class="btn btn-success btn-s" (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
                        <span class="glyphicon glyphicon-upload"></span>
                        Upload all
                    </button>
                        
                    <button type="button" class="btn btn-warning btn-s" (click)="uploader.cancelAll()" [disabled]="!uploader.isUploading">
                        <span class="glyphicon glyphicon-ban-circle"></span>
                        Cancel all
                    </button>
                    
                    <button type="button" class="btn btn-danger btn-s" (click)="uploader.clearQueue()" [disabled]="!uploader.queue.length">
                        <span class="glyphicon glyphicon-trash"></span>
                        Remove all
                    </button>
                
                    <br/><br/><br/>
                    <div id="hiddenDiv">
                        <div class="resultsSummary" id="SpanContainer">
                        <span class="block" style="float:left!important;text-transform:uppercase"><br/>Upload Log</span> <br/><br/><br/>
                            <div *ngFor="let item of uploader.queue" >                      
                                <div class="row" *ngIf="item.isSuccess">
                                    <br/>
                                    <span class="block">
                                        <hr style= "border-bottom: 1px solid gray; width:20%"/>
                                        {{ response }} - {{ item?.file?.name }}  {{ item?.file?.lastModifiedDate }}
                                    </span>
                                    <br/><br/>
                                </div>  
                            </div>
                        </div>
                    </div>
                </div>
                
                <table class="table">
                    <thead>
                        <tr>
                            <th width="10%">File Name</th>
                            <th width="10%">Size</th>
                            <th width="10%">Progress</th>
                            <th width="10%">Status</th>
                            <th width="10%">Actions</th>
                        </tr>
                    </thead>
                
                    <tbody>
                        <tr *ngFor="let item of uploader.queue">
                            <td><strong>{{ item?.file?.name }}</strong></td>
                            <td>
                                {{ DisplaySize(item) }}
                            </td>
                        <td>
                        <div class="progress" style="margin-bottom: 0;">
                            <div class="progress-bar progress-bar-striped bg-danger" role="progressbar"  style="width: 75%" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" [ngStyle]="{ 'width': item.progress + '%' }"></div>
                        </div>
                        </td>
                         <td class="text-center">
                            <span *ngIf="item.isSuccess"><i class="glyphicon glyphicon-ok" style="color:red"></i></span>
                            <span *ngIf="item.isCancel"><i class="glyphicon glyphicon-ban-circle"></i></span>
                            <span *ngIf="item.isError"><i class="glyphicon glyphicon-remove"></i></span>
                        </td>
                        <td no-wrap>
                            <button type="button" class="btn btn-danger btn-s" (click)="item.remove()">
                                <span class="glyphicon glyphicon-trash"></span>
                            </button>
                        </td>
                    </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>  
</ion-content>
        
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

home.scss

page-home {
    #containerdiv { margin-top: 50px }
    #resultsdiv { border: dotted 3px gray; width: 70%; background-color: white; border-radius: 5px }
    #containerdiv h3 { color: white; text-transform: uppercase; font-size:17px; bottom:480px; position: absolute; float:right; right: 380px }
    .my-drop-zone { border: dotted 3px #842024; height: 250px; background-color: white; border-radius: 5px; width:800px; margin-top:10px; margin-left: -40px }
    .container { width: 80% }
    .nv-file-over { border: solid 3px black; }
    .drag-label { color: #cccccc; margin-top: 110px; text-align: center }
    .title-label { margin-left:100px; color: black; text-transform:uppercase }
    .navbar { width:100% !important }
    .results { margin-top: 30px; margin-left: 30px; color: darkgray }
    .results p { color: #191919; float: center; text-transform: none; font-weight: 200; line-height: 1em; font-size:12px; font-weight:bold; margin-left:-30px; margin-top: -10px }
    .results th { color: black; text-transform: none; font-weight: 100; line-height: 1em; font-style: italic; opacity: 0.8; font-size:17px }
    .progressdiv { position:absolute; bottom: 100px; right:-350px }
    .dropbtn { background-color: darkred; color: white; padding: 16px; font-size: 12px; border: none }
    .dropdown { position: relative; display: inline-block }
    .dropdown-content { display: none; position: absolute; background-color: darkred; min-width: 160px;box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); z-index: 1 }
    .dropdown-content a { color: black; padding: 12px 16px; text-decoration: none; display: block }
    .dropdown-content a:hover { background-color: #ddd }
    .dropdown:hover .dropdown-content { display: block }
    .dropdown:hover .dropbtn { background-color: black }
     html, body { height: 100%; background: #842024 }   
}

Any help is really appreciated!

Thanks,
Ryan


#2

So does the network tab say that the upload worked?
Why do you then think it did not work?


#3

That’s correct. The network tab shows no problem, but the file does not get uploaded when I use the Ionic code. It uploads fine with Postman.


#4

What does this mean in this context? If the network request is correct and your server responds with the correct status code, shouldn’t it have worked then? Why do you then think it did not work?


#5

Well I know the Node server (app.js file) works because Postman is able to upload the file.

Ionic says that there’s no issue, so I believe the problem is that my Ionic code does not build the form body correctly.


#6

Last time I ask this question:
Why do you then think it did not work?

Does the network request in the Chrome network panel look wrong?
Does the Node server not get the data it should?


#7

Hi Jan,

It doesn’t work because the file that I try uploading doesn’t get saved to the server. I’m at a loss how to check where the problem happens because I don’t know how to use Chrome to show the exact form data that Ionic sends out.

I do know that the problem is not with the Node server because Node + Postman works. Node + Ionic does not work. Any help on how to debug the issue is really appreciated!

Thanks,
Ryan


#8

Clicking around in the network panel should be able to show you the exact request - you can for example also export it as a curl request.

You can somehow also do that in Postman, googling should give you instructions on how to enable the dev tools for Postman. Then you can just compare the two.

You can also edit and re-run requests in Chrome dev tools network panel to try different things.


#9

Solution & tutorial posted here:

Thanks,
Ryan