Академический Документы
Профессиональный Документы
Культура Документы
In this tutorial, I will be discussing the approach of uploading files using ngResource with AngularJS.
Introduction
The traditional way of uploading a file is to use a form submission. This approach works extremely well with application
implemented with Spring MVC or ASP.NET MVC frameworks. In this tutorial, I will be discussing a different approach of uploading a
file. In this tutorial, the file upload can be packed inside a JSON object and handled by a RESTful web service.
This tutorial will begin with an overview of the sample application's architecture. Then, I will discuss the way of creating a file upload
HTML component using Bootstrap. Afterwards, we will explore the way AngularJS can be used to manipulate the HTML elements.
Believe me, manipulating the HTML elements like JQuery is necessary in this tutorial. And we will get to use $scope.$apply().
Files from local drive can be read by JavaScript's FileReader object, which transforms binary into BASE64 encoded text string.
This allows us to pack the file content into a JSON object to be sent. On the server side, the request handling method will parse the
file content and revert the BASE64 string back into a binary file.
The application also contains an index page which is actually a single-page web application implemented using AngularJS. The
application has a single input that can load a file from disk. Once file is selected, the application will load the file content and get the
file name (just the file name, not the path). It packs the two into a JSON object and sends it to the RESTFul web service.
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 1/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
How to manipulate the HTML elements on the page to select a file from local disk drive.
How to read the file and transform the file into a string so that it can be transported to the backend web service.
On server side, how to read the input file content and save it back as a file.
All these will be explained in the next few sections. When I first started working on the sample application, the very first task I had to
do is design an HTML element that can get a file from local disk. Let's see how this is done.
<div class="input-group">
<input type="file" id="fileUploadField" style="display: none;">
<input type="text" id="fileNameDisplayField" class="form-control"
In the code snippet above, there are some attributes specific for AngularJS. You can ignore these for now. The outer <div>
element has CSS class "input-group". It can smash multiple input control into one. With the above code snippet, it will create
an input control like this:
An input field with type of "file". This field will be invisible because I am not going to display it. It exists so that I can use it
to hold the metadata of the chosen file. And I use its functionality to choose a file.
A text input file that is readonly, can be used to display the file name (without the folder path). I choose it to be readonly
because I don't want the user to modify the value. This is just a preference. If you wish, you can make it editable.
A button with a File icon. It can be click and select a file. The button defined via another div using CSS class "input-
group-addon".
Let's circle back, to the question why I don't use the HTML default file controller. If you make it visible by removing the style
attribute, you can see that it is a text field with a button. It is similar to the component I just created. The ugly aspect of this default
one is that it is not customizable. And it will not fit with the other Bootstrap controls. It is best to create something that looks like
the rest of the components. And, there, you have it.
Now we have the file upload component. It is time to implement the behavior which can select a file and pack it as an JSON object.
How to click the button with the file icon to pop up the File Open dialog.
Once a file is selected, how to save the file metadata and extract the single file name to display in the readonly text field.
How to load the file content as BASE64 string.
What is not really a challenge is sending the BASE64 file content string to the backend web service. This is done with
ngResource. I will discuss this at the end of this section.
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 2/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
Since I have to define my own file upload component and the default file upload input is hidden, there has to be a way to link the
button click to the default file upload input. Turns out, it is pretty easy to do. The default file upload input has a button. All I need to
do is associate my button click to the click of the file upload button click.
In order to do this, I have to use something similar to JQuery. AngularJS has a built-in function called angular.element(). It
can be used to select elements from the page. Before I continue, I just want to say that it is OK to query the DOM elements in the
page. My reasons are:
Back to the design. When a user clicks on the button with the file icon, it will trigger the hidden file upload input to be clicked. Here
is how:
vm.clickSelectFile = function () {
angular.element("#fileUploadField").click();
};
On the index.html, the button with the file icon is defined as this:
As you can see, the directive ng-click references the function vm.clickSelectFile(). So when this button is clicked, the
JavaScript code above will do a query on the DOM tree and find the hidden input of file upload and invoke its click() method.
When the user does the click, the File Open dialog will popup. After choosing a file and closing this popup, the read-only text field
does not display anything. This is expected unless I add more functionalities to the application. The next functionality I had to add is
that when the hidden file upload input is assigned a file, it will notify the text field to display the file name, which is discussed next.
To have the file upload input handle the selection change event, what I had to do is use angular.element() to query the
hidden file upload input and assign an event handling method to it:
angular.element("#fileUploadField").bind("change", function(evt) {
if (evt) {
...
}
});
As you can see, this is the same way one would do with JQuery. Query the element by its id to get a reference, then bind the event
handling method to the event. In this case, it is the change event of the element that will be handled.
Next, I need to use the full path file name of the selected file, and return only the file name, not the folder path. For this, I just came
up with a simple heuristic. The file name will either be a Windows full path file name with drive letter, and backslashes; or UNIX
based full path file name with slashes. It is one way or the other, so here is how I extract the file name:
var fn = evt.target.value;
if (fn && fn.length > 0) {
var idx = fn.lastIndexOf("/");
if (idx >= 0 && idx < fn.length) {
vm.uploadFileName = fn.substring(idx+1);
} else {
idx = fn.lastIndexOf("\\");
if (idx >= 0 && idx < fn.length) {
vm.uploadFileName = fn.substring(idx+1);
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 3/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
}
}
}
$scope.$apply();
The code snippet above first looks for the last slash in the file name. If found, I just take the rest of the file name after this character.
If the last slash is not found, then I will try again by finding the last back slash in the file name. If found, then I again will take the rest
of the file name after this character. If both characters are not found, nothing is changed for the scope variable.
vm.uploadFileName is the scope variable. Once the file name is assigned to the scope variable, I need to call
$scope.$apply() to make sure the text field would display the file name. Why would I do this? Normally, AngularJS would
handle view element to model binding automatically. But, in this case where I explicitly query the element and bind an event
handler method, the automatic binding of the data model to view is not set. So to explicitly do the model to view data refresh, just
call $scope.$apply().
What is next? It is the hardest part of this tutorial. I need to load the file as BASE64 encoded string. It will be discussed next.
All I have to do is pass the file name to the object's readAsDataURL() to do the file loading.
The loading of file is done asynchronously. And when the loading is done, I can provide a callback method that can call the
web service to do the actual upload.
Here is the entire source code of the file loading, and upload the file:
vm.doUpload = function () {
vm.uploadSuccessful = false;
var elems = angular.element("#fileUploadField");
if (elems != null && elems.length > 0) {
if (elems[0].files && elems[0].files.length > 0) {
let fr = new FileReader();
fr.onload = function(e) {
if (fr.result && fr.result.length > 0) {
var uploadObj = {
fileName: vm.uploadFileName,
uploadData: fr.result
};
sampleUploadService.uploadImage(uploadObj).then(function(result) {
if (result && result.success === true) {
clearUploadData();
vm.uploadSuccessful = true;
}
}, function(error) {
if (error) {
console.log(error);
}
});
}
};
fr.readAsDataURL(elems[0].files[0]);
} else {
vm.uploadObj.validationSuccess = false;
vm.uploadObj.errorMsg = "No file has been selected for upload.";
}
}
};
There are a couple points regarding the above code that are worthy of mention. First, I need to get the file name (full path) of the
selected file. By querying the element of the default upload file input, I will get a reference of it. Then, I will use its property called
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 4/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
.files to get a reference to all the selected files of this input element. The default file upload input can select multiple files at
once, this is why an array is used to hold these files instead of an object reference to just a single file. Here is the code snippet:
Next, I need to create a FileReader object, and read the file using method readAsDataUrl(). Here is the code snippet:
fr.readAsDataURL(elems[0].files[0]);
As shown above, I created an object of FileReader and have variable fr reference it. Then, I supplied the object with a call back
method for its property onload. The method supplied will call my AngularJS service object to do the file upload. Next, I will
discuss how this service object works.
(function () {
"use strict";
var mod = angular.module("uploadServiceModule", [ "ngResource" ]);
mod.factory("sampleUploadService", [ "$resource",
function ($resource) {
var svc = {};
return svc;
}
])
})();
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 5/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
If you have read my previous article, "AngularJS ngResource Tutorial", it is going to be easy for you to understand the above service
object defintion. I defined a factory object called "sampleUploadService". The factory object returns an object that acts as a
service, in it, there is just one method called uploadImage().
The uploadImage() method uses ngResource object to call the backend web service. The ngResource object which I
have defined has just one action method inside, also called "uploadImage". The action method sees HTTP Post for
communication with the back end. The url property defines the web service url. And it expects a single object as response, not
an array of objects. Finally, the data that is sent to the backend will be a JSON object, which has two properties: one is the file name
and the other is BASE64 encoded file content.
Let's get back to the end of the last section, where I was about to post the code snippet of how to use the above AngularJS service
(or shall we say factory) to invoke the backend web service. I stop there because I think it is best to show how the AngularJS service
is defined first. Now that is revealed, time to see how the AngularJS service can be used. Here it is:
var uploadObj = {
fileName: vm.uploadFileName,
uploadData: fr.result
};
sampleUploadService.uploadImage(uploadObj).then(function(result) {
if (result && result.success === true) {
clearUploadData();
vm.uploadSuccessful = true;
}
}, function(error) {
if (error) {
console.log(error);
}
});
The file content is stored in the property "result" of the FileReader object. It is only available after the call of
readAsDataURL() is completed asynchronously. When it is done, the object's onload() callback will be invoked, where the
above code snippet is located. In the above code snippet, I created a new object called uploadObj. And I assigned the file name
without the full path and the file content which is in fr.result to the properties of the new object.
Finally, I used my service sampleUploadService and called the uploadImage(). That is all it took. It is very simple. If you
read my previous tutorial, you will know how to handle the event when the HTTP call finishes.
The last thing I want to cover here is the format of the file content. It is not just the simple BASE64 encoded string. The first
part is the media type, start with the prefix "data:". It is followed by a semicolon. Then it is the encoding type. This will always be
"base64", and followed by a comma. Finally it is the BASE64 encoded string. Here is a short sample:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZkAAAHVCAIAAACs
XRGOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjw...
This is the last of the JavaScript code logic. In the next section, I will cover the Java side of code logic.
package org.hanbo.boot.rest.models;
I also need an object as a response. For this, I created another object type called GenericResponse. Here it is:
package org.hanbo.boot.rest.models;
The final part of this tutorial, of the sample program, is the RESTFul controller that handles the file upload. In order to demo the
decoding of the file data, I created the request handling method to decode the file content, and save the file to disk.
@RequestMapping(value="/uploadImage", method=RequestMethod.POST)
public ResponseEntity<GenericResponse> uploadImage(
@RequestBody
UploadObject uploadObj
)
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 7/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
{
if (uploadObj != null && uploadObj.getUploadData() != null)
{
String uploadData = uploadObj.getUploadData();
if (uploadData.length() > 0)
{
String[] splitData = uploadData.split(";");
if (splitData != null && splitData.length == 2)
{
String mediaType = splitData[0];
System.out.println(mediaType);
if (splitData[1] != null && splitData[1].length() > 0)
{
String[] splitAgain = splitData[1].split(",");
if (splitAgain != null && splitAgain.length == 2)
{
String encodingType = splitAgain[0];
System.out.println(encodingType);
String imageValue = splitAgain[1];
return retVal;
}
In above code snippet, all I'm doing is splitting the string to get to the BASE64 encoded part, which is the actual file content.
Once I got the string, I use Apache Commons' encoding library to turn the string into a byte array. Finally, I save the byte
array to a file located in folder: C:\temp. The file name is the file name from the request. After saving the file, the method returns
back a JSON object of type GenericResponse to the caller.
How to Test
After downloading the source code, please go through all the static content files and rename all the files *.sj to *.js. I had to rename
these files so I can zip them up and send to codeproject.com via email for publishing. The email server scans the zip files and won't
allow me to attach the file because JavaScript files are inside. Anyways, you need to rename these files if you want to run them
locally.
The sample application is a Spring Boot based application, so it must be built before you can start it as a service application. To
build it, you can CD into the base director the sample application. Then run:
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 8/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
The build will succeed. After it is successful, run the following command and start the sample application:
The application will start successfully. When it does, you will see the following output in the commandline console:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.5.RELEASE)
To run the web application in the browser, navigate to the following URL:
http://localhost:8080
Once the page loads, it will display the screenshot displayed earlier in this tutorial. All you need to do is click the button with the file
icon, the File Open dialog would pop up. Use it to select a file and click OK. You will see the file name without the full folder path
would be displayed on the text input. At last, click on the blue button "Upload". When upload succeeds, a green status bar would
display on top of the page and show upload succeeded.
Go to the destination folder C:\temp, and find the file that was just uploaded and saved there.
Summary
This is yet another good tutorial completed. Again, I had fun writing this. For this tutorial, my focus is on file upload, specifically on
how to perform file upload using AngularJS' ngResource. As shown, in order to perform the file upload, I had to load the file and
convert the content into BASE64 encoded string (along with the media type and encoding type). Then this can be packed in a
JSON object, which can be sent to the web service.
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 9/10
05/08/2019 Upload File with AngularJS using ngResource - CodeProject
The web service is written as a Spring Boot web application. The single page application is also packaged in the same application.
The web service has just one request handling method, which takes the JSON object as input. Once it received the request, it will
parse the BASE64 encoded string, converted into a byte array. Then save the byte array into a file. Even though this sample
application does not do anything useful, it demonstrates the upload functionality end to end. It is useful for anyone who needs this.
I hope you enjoyed this tutorial. I certainly had fun writing this. Anyways, enjoy this and I hope it is helpful.
History
7/31/2019 - Initial draft
License
This article, along with any associated source code and files, is licensed under The MIT License
https://www.codeproject.com/Articles/5163873/Upload-File-with-AngularJS-using-ngResource?display=Print 10/10