GitHub - danialfarid/ng-file-upload: Lightweight Angular directive to upload files with optional FileAPI shim for cross browser support (original) (raw)

npm version Downloads Issue Stats Issue Stats
PayPayl donate button Gratipay donate button

ng-file-upload

Lightweight Angular directive to upload files.

See the DEMO page. Reference docs here

Migration notes: version 3.0.x version 3.1.x version 3.2.x version 4.x.x version 5.x.x version 6.x.x version 6.2.x version 7.0.x version 7.2.x version 8.0.x version 9.0.x version 10.0.x version 11.0.x version 12.0.x version 12.1.x version 12.2.x

Ask questions on StackOverflow under the ng-file-upload tag.
For bug report or feature request please search through existing issues first then open a new one here. For faster response provide steps to reproduce/versions with a jsfiddle link. If you need support for your company contact me.
If you like this plugin give it a thumbs up at ngmodules or get me a cup of tea . Contributions are welcomed.

Table of Content:

Features

Install

Usage

Samples:

Upload on form submit or button click

Single Image with validations
Select
Multiple files
Select
Drop files:
Drop
submit

Upload right away after file selection:

Upload on file select
Upload on file select
Drop File:
Drop Images or PDFs files here
File Drag/Drop is not supported for this browser

Image thumbnail: Audio preview: Video preview:

Javascript code:

//inject directives and services. var app = angular.module('fileUpload', ['ngFileUpload']);

app.controller('MyCtrl', ['$scope', 'Upload', function ($scope, Upload) { // upload later on form submit or something similar $scope.submit = function() { if ($scope.form.file.$valid && $scope.file) { scope.upload(scope.upload(scope.upload(scope.file); } };

// upload on file select or drop
$scope.upload = function (file) {
    Upload.upload({
        url: 'upload/url',
        data: {file: file, 'username': $scope.username}
    }).then(function (resp) {
        console.log('Success ' + resp.config.data.file.name + 'uploaded. Response: ' + resp.data);
    }, function (resp) {
        console.log('Error status: ' + resp.status);
    }, function (evt) {
        var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
        console.log('progress: ' + progressPercentage + '% ' + evt.config.data.file.name);
    });
};
// for multiple files:
$scope.uploadFiles = function (files) {
  if (files && files.length) {
    for (var i = 0; i < files.length; i++) {
      Upload.upload({..., data: {file: files[i]}, ...})...;
    }
    // or send them all together for HTML5 browsers:
    Upload.upload({..., data: {file: files}, ...})...;
  }
}

}]);

Full reference

File select and drop

At least one of the ngf-select or ngf-drop are mandatory for the plugin to link to the element.ngf-select only attributes are marked with * and ngf-drop only attributes are marked with +.

<div|button|input type="file"|ngf-select|ngf-drop... ngf-select="" or "upload($files, ...)" ngf-drop="" or "upload($files, ...)" ngf-change="upload($files, file,file, file,newFiles, duplicateFiles,duplicateFiles, duplicateFiles,invalidFiles, $event)" ng-model="myFiles" ngf-model-options="{updateOn: 'change click drop dropUrl paste', allowInvalid: false, debounce: 0}" ngf-model-invalid="invalidFile(s)" ngf-before-model-change="beforeChange($files, ...)" ng-disabled="boolean" ngf-select-disabled="boolean" ngf-drop-disabled="boolean" ngf-multiple="boolean" ngf-keep="true|false|'distinct'" ngf-fix-orientation="boolean"

*ngf-capture="'camera'" or "'other'" ngf-accept="'image/'"

+ngf-allow-dir="boolean" +ngf-include-dir="boolean" +ngf-drag-over-class="{pattern: 'image/*', accept:'acceptClass', reject:'rejectClass', delay:100}" or "'myDragOverClass'" or "calcDragOverClass($event)" +ngf-drag="drag($isDragging, class,class, class,event)" +ngf-drop-available="dropSupported" +ngf-stop-propagation="boolean" +ngf-hide-on-drop-not-available="boolean" +ngf-enable-firefox-paste="boolean"

ngf-resize="{width: 100, height: 100, quality: .8, type: 'image/jpeg', ratio: '1:2', centerCrop: true, pattern='.jpg', restoreExif: false}" or resizeOptions() ngf-resize-if="$width > 1000 || height>1000"or"resizeCondition(height > 1000" or "resizeCondition(height>1000"or"resizeCondition(file, width,width, width,height)" ngf-validate-after-resize="boolean"

ngf-max-files="10" ngf-pattern="'.pdf,.jpg,video/*,!.jog'" ngf-min-size, ngf-max-size, ngf-max-total-size="100" in bytes or "'10KB'" or "'10MB'" or "'10GB'" ngf-min-height, ngf-max-height, ngf-min-width, ngf-max-width="1000" in pixels only images ngf-ratio="8:10,1.6" ngf-min-ratio, ngf-max-ratio="8:10" ngf-dimensions="$width > 1000 || height>1000"or"validateDimension(height > 1000" or "validateDimension(height>1000"or"validateDimension(file, width,width, width,height)" ngf-min-duration, ngf-max-duration="100.5" in seconds or "'10s'" or "'10m'" or "'10h'" only audio, video ngf-duration="$duration > 1000" or "validateDuration($file, $duration)"

ngf-validate="{size: {min: 10, max: '20MB'}, width: {min: 100, max:10000}, height: {min: 100, max: 300} ratio: '2x1', duration: {min: '10s', max: '5m'}, pattern: '.jpg'}" ngf-validate-fn="validate($file)" ngf-validate-async-fn="validate($file)" ngf-validate-force="boolean" ngf-ignore-invalid="'pattern maxSize'" ngf-run-all-validations="boolean"

Upload/Drop

<div|... ngf-no-file-drop>File Drag/drop is not supported

image

File preview

<img|audio|video|div *ngf-src="file" *ngf-background="file" ngf-resize="{width: 20, height: 20, quality: 0.9}" ngf-no-object-url="true or false"

<div|span|... *ngf-thumbnail="file" ngf-size="{width: 20, height: 20, quality: 0.9}" the image will be resized to this size ngf-as-background="boolean"

Upload service:

var upload = Upload.upload({ url: 'server/upload/url', // upload.php script, node.js route, or servlet url / Specify the file and optional data to be sent to the server. Each field including nested objects will be sent as a form data multipart. Samples: {pic: file, username: username} {files: files, otherInfo: {id: id, person: person,...}} multiple files (html5) {profiles: {[{pic: file1, username: username1}, {pic: file2, username: username2}]} nested array multiple files (html5) {file: file, info: Upload.json({id: id, name: name, ...})} send fields as json string {file: file, info: Upload.jsonBlob({id: id, name: name, ...})} send fields as json blob, 'application/json' content_type {picFile: Upload.rename(file, 'profile.jpg'), title: title} send file with picFile key and profile.jpg file name*/ data: {key: file, otherInfo: uploadInfo}, / This is to accommodate server implementations expecting nested data object keys in .key or [key] format. Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file data: {rec: {name: 'N', pic: file}}, objectKey: '.k' sent as: rec.name -> N, rec.pic -> file / objectKey: '[k]' or '.k' // default is '[k]' / This is to accommodate server implementations expecting array data object keys in '[i]' or '[]' or ''(multiple entries with same key) format. Example: data: {rec: [file[0], file[1], ...]} sent as: rec[0] -> file[0], rec[1] -> file[1],... data: {rec: {rec: [f[0], f[1], ...], arrayKey: '[]'} sent as: rec[] -> f[0], rec[] -> f[1],.../ arrayKey: '[i]' or '[]' or '.i' or '' //default is '[i]' method: 'POST' or 'PUT'(html5), default POST, headers: {'Authorization': 'xxx'}, // only for html5 withCredentials: boolean, / See resumable upload guide below the code for more details (html5 only) */ resumeSizeUrl: '/uploaded/size/url?file=' + file.name // uploaded file size so far on the server. resumeSizeResponseReader: function(data) {return data.size;} // reads the uploaded file size from resumeSizeUrl GET response resumeSize: function() {return promise;} // function that returns a prommise which will be // resolved to the upload file size on the server. resumeChunkSize: 10000 or '10KB' or '10MB' // upload in chunks of specified size disableProgress: boolean // default false, experimental as hotfix for potential library conflicts with other plugins ... and all other angular $http() options could be used here. })

// returns a promise upload.then(function(resp) { // file is uploaded successfully console.log('file ' + resp.config.data.file.name + 'is uploaded successfully. Response: ' + resp.data); }, function(resp) { // handle error }, function(evt) { // progress notify console.log('progress: ' + parseInt(100.0 * evt.loaded / evt.total) + '% file :'+ evt.config.data.file.name); }); upload.catch(errorCallback); upload.finally(callback, notifyCallback);

/* access or attach event listeners to the underlying XMLHttpRequest */ upload.xhr(function(xhr){ xhr.upload.addEventListener(...) });

/* cancel/abort the upload in progress. */ upload.abort();

/* alternative way of uploading, send the file binary with the file's content-type. Could be used to upload files to CouchDB, imgur, etc... html5 FileReader is needed. This is equivalent to angular $http() but allow you to listen to the progress event for HTML5 browsers.*/ Upload.http({ url: '/server/upload/url', headers : { 'Content-Type': file.type }, data: file })

/* Set the default values for ngf-select and ngf-drop directives*/ Upload.setDefaults({ngfMinSize: 20000, ngfMaxSize:20000000, ...})

// These two defaults could be decreased if you experience out of memory issues // or could be increased if your app needs to show many images on the page. // Each image in ngf-src, ngf-background or ngf-thumbnail is stored and referenced as a blob url // and will only be released if the max value of the followings is reached. Upload.defaults.blobUrlsMaxMemory = 268435456 // default max total size of files stored in blob urls. Upload.defaults.blobUrlsMaxQueueSize = 200 // default max number of blob urls stored by this application.

/* Convert a single file or array of files to a single or array of base64 data url representation of the file(s). Could be used to send file in base64 format inside json to the databases */ Upload.base64DataUrl(files).then(function(urls){...});

/* Convert the file to blob url object or base64 data url based on boolean disallowObjectUrl value */ Upload.dataUrl(file, boolean).then(function(url){...});

/* Get image file dimensions*/ Upload.imageDimensions(file).then(function(dimensions){console.log(dimensions.width, dimensions.height);});

/* Get audio/video duration*/ Upload.mediaDuration(file).then(function(durationInSeconds){...});

/* Resizes an image. Returns a promise */ // options: width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif //resizeIf(width, height) returns boolean. See ngf-resize directive for more details of options. Upload.resize(file, options).then(function(resizedFile){...});

/* returns boolean showing if image resize is supported by this browser*/ Upload.isResizeSupported() /* returns boolean showing if resumable upload is supported by this browser*/ Upload.isResumeSupported()

/* returns a file which will be uploaded with the newName instead of original file name / Upload.rename(file, newName) / converts the object to a Blob object with application/json content type for jsob byte streaming support #359 (html5 only)/ Upload.jsonBlob(obj) / converts the value to json to send data as json string. Same as angular.toJson(obj) / Upload.json(obj) / converts a dataUrl to Blob object./ var blob = upload.dataUrltoBlob(dataurl, name); / returns true if there is an upload in progress. Can be used to prompt user before closing browser tab / Upload.isUploadInProgress() boolean / downloads and converts a given url to Blob object which could be added to files model / Upload.urlToBlob(url).then(function(blob) {...}); / returns boolean to check if the object is file and could be used as file in Upload.upload()/http() / Upload.isFile(obj); / fixes the exif orientation of the jpeg image file*/ Upload.applyExifRotation(file).then(...)

ng-modelThe model value will be a single file instead of an array if all of the followings are true:

validationWhen any of the validation directives specified the form validation will take place and you can access the value of the validation using myForm.myFileInputName.$error.<validate error name>for example form.file.$error.pattern. If multiple file selection is allowed you can specify ngf-model-invalid="invalidFiles" to assing the invalid files to a model and find the error of each individual file with file.$error and description of it with file.$errorParam. You can use angular ngf-model-options to allow invalid files to be set to the ng-model ngf-model-options="{allowInvalid: true}".

Upload multiple files: Only for HTML5 FormData browsers (not IE8-9) you have an array of files or more than one file in your data to send them all in one request . Non-html5 browsers due to flash limitation will upload each file one by one in a separate request. You should iterate over the files and send them one by one for a cross browser solution.

drag and drop styling: For file drag and drop, ngf-drag-over-class could be used to style the drop zone. It can be a function that returns a class name based on the $event. Default is "dragover" string. Only in chrome It could be a json object {accept: 'a', 'reject': 'r', pattern: 'image/*', delay: 10} that specify the class name for the accepted or rejected drag overs. The pattern specified or ngf-pattern will be used to validate the file's mime-typesince that is the only property of the file that is reported by the browser on drag. So you cannot validate the file name/extension, size or other validations on drag. There is also some limitation on some file types which are not reported by Chrome.delay default is 100, and is used to fix css3 transition issues from dragging over/out/over #277.

Upload.setDefaults(): If you have many file selects or drops you can set the default values for the directives by calling Upload.setDefaults(options). options would be a json object with directive names in camelcase and their default values.

**Resumable Uploads:**The plugin supports resumable uploads for large files. On your server you need to keep track of what files are being uploaded and how much of the file is uploaded.

Old browsers

For browsers not supporting HTML5 FormData (IE8, IE9, ...) FileAPI module is used.Note: You need Flash installed on your browser since FileAPI uses Flash to upload files.

These two files FileAPI.min.js, FileAPI.flash.swf will be loaded by the module on demand (no need to be included in the html) if the browser does not supports HTML5 FormData to avoid extra load for HTML5 browsers. You can place these two files beside angular-file-upload-shim(.min).js on your server to be loaded automatically from the same path or you can specify the path to those files if they are in a different path using the following script:

...

Old browsers known issues:

Server Side

CORS

To support CORS upload your server needs to allow cross domain requests. You can achieve that by having a filter or interceptor on your upload file server to add CORS headers to the response similar to this: (sample java code)

httpResp.setHeader("Access-Control-Allow-Methods", "POST, PUT, OPTIONS"); httpResp.setHeader("Access-Control-Allow-Origin", "your.other.server.com"); httpResp.setHeader("Access-Control-Allow-Headers", "Content-Type"));

For non-HTML5 IE8-9 browsers you would also need a crossdomain.xml file at the root of your server to allow CORS for flash:(sample xml)

Amazon AWS S3 Upload

For Amazon authentication version 4 see this comment

The demo page has an option to upload to S3. Here is a sample config options:

Upload.upload({ url: 'https://angular-file-upload.s3.amazonaws.com/', //S3 upload url including bucket name method: 'POST', data: { key: file.name, // the key to store the file on S3, could be file name or customized AWSAccessKeyId: , acl: 'private', // sets the access to the uploaded file in the bucket: private, public-read, ... policy: $scope.policy, // base64-encoded json policy (see article below) signature: $scope.signature, // base64-encoded signature based on policy string (see article below) "Content-Type": file.type != '' ? file.type : 'application/octet-stream', // content type of the file (NotEmpty) filename: file.name, // this is needed for Flash polyfill IE8-9 file: file } });

This article explains more about these fields and provides instructions on how to generate the policy and signature using a server side tool. These two values are generated from the json policy document which looks like this:

{ "expiration": "2020-01-01T00:00:00Z", "conditions": [ {"bucket": "angular-file-upload"}, ["starts-with", "$key", ""], {"acl": "private"}, ["starts-with", "$Content-Type", ""], ["starts-with", "$filename", ""], ["content-length-range", 0, 524288000] ] }

The demo page provide a helper tool to generate the policy and signature from you from the json policy document. Note: Please use https protocol to access demo page if you are using this tool to generate signature and policy to protect your aws secret key which should never be shared.

Make sure that you provide upload and CORS post to your bucket at AWS -> S3 -> bucket name -> Properties -> Edit bucket policy and Edit CORS Configuration. Samples of these two files:

{ "Version": "2012-10-17", "Statement": [ { "Sid": "UploadFile", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::xxxx:user/xxx" }, "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::angular-file-upload/" }, { "Sid": "crossdomainAccess", "Effect": "Allow", "Principal": "", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::angular-file-upload/crossdomain.xml" } ] }

http://angular-file-upload.appspot.com POST GET HEAD 3000 *

For IE8-9 flash polyfill you need to have a crossdomain.xml file at the root of you S3 bucket. Make sure the content-type of crossdomain.xml is text/xml and you provide read access to this file in your bucket policy.

You can also have a look at https://github.com/nukulb/s3-angular-file-upload for another example with this fix.