One issue you may run into with the FileTransfer plugin is that it only lets you do one transfer at a time. You can get around this by using XHR2 (for uploads anyway), but I thought it would be nice to demonstrate how to work with multiple transfers using promises. The FileTransfer plugin does not use promises by default, but luckily you can simply use ngCordova and use the promisified (that's a word) version of the plugin.
For this demo I'm using Ionic and ngCordova, although in theory you can use ngCordova without Ionic. I created a new application with the blank template. I then used bowser to install ngCordova, which wasn't actually necessary since the CLI supports adding it:
@raymondcamden @Ionicframework Already there! Just need to document it. ionic add ngCordova
— uoʇƃuıʇɹɐɥ ǝʞıɯ (@mhartington) April 13, 2015
I then whipped up the following demo. I don't like putting everything in one JS file for my Angular projects, but since this is just a demo, I guess that's ok.
angular.module('starter', ['ionic','ngCordova'])
.config(['$compileProvider', function($compileProvider) {
$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|content|file|assets-library):/);
}])
.controller('Main', ["$ionicPlatform", "$cordovaFileTransfer", "$q", "$scope",
function($ionicPlatform, $cordovaFileTransfer, $q, $scope) {
console.log("running Main controller");
$scope.images = [];
$ionicPlatform.ready(function() {
//resources to download
var resources = [
"https://placekitten.com/g/200/300",
"https://placekitten.com/g/200/350",
"https://placekitten.com/g/400/350",
"https://placekitten.com/g/300/200",
"https://placekitten.com/g/188/188"
];
var promises = [];
resources.forEach(function(i,x) {
var targetPath = cordova.file.documentsDirectory + "image"+x+".jpg";
promises.push($cordovaFileTransfer.download(i, targetPath, {}, true));
});
$q.all(promises).then(function(res) {
console.log("in theory, all done");
for(var i=0; i<res.length; i++) {
$scope.images.push(res[i].nativeURL);
}
});
});
}])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault();
}
});
});
Ok, let's take this from the top. First off - the config block is just there to allow for File based URIs in my dom. Angular is a bit anal (ok, secure) about what it allows for image urls. The next bit is my controller, Main. I've got 5 URLs for different kittens at placekitten.com. I create an array to store my promises, and then just loop over the URLs. For each one I call the ngCordova-wrapped file transfer download method. Since it returns a promise I end up with an array of promises.
Finally, I use Angular's all method for their $q library to simply say, "Do this when they are all done." I then push the final URLs into an array that is used in my view. Here's the index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title></title>
<link href="lib/ionic/css/ionic.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
<link href="css/ionic.app.css" rel="stylesheet">
-->
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/ngCordova/dist/ng-cordova.js"></script>
<script src="cordova.js"></script>
<!-- your app's js -->
<script src="js/app.js"></script>
</head>
<body ng-app="starter">
<ion-pane>
<ion-header-bar class="bar-stable">
<h1 class="title">Ionic Blank Starter</h1>
</ion-header-bar>
<ion-content ng-controller="Main">
<div ng-repeat="img in images">
<img ng-src="{{img}}">
</div>
</ion-content>
</ion-pane>
</body>
</html>
And that's it. The result is a set of cat pictures, which, to be honest, is what all apps should end up with:
Similar code would work for uploads as well. Again, you do not need to use Ionic/ngCordova for this. You could create your own promises and do this by hand with a bit more work. (I've got a vide on deferreds and jQuery that may make this easier for you.)
Archived Comments
That's exactly what I came up with also. Nice tutorial ! Thanks :)
how can I move the code for downloading from controller to a service?
Just do it? I mean - just put it in a service and call the service method.
Sorry for the stupid question. I was able to create a service after 5 min of having a mug of coffee :)
Nah, not stupid. As it stands, when I was learning Angular (wait, I still am), I did a lot in my controllers and rarely used services. This post should use a service!
Again, great post.
One question though, I am working on app where on the first start I have to download 400 image assets. It looks like $q.all is trying to run all at the same time which is practically crashing the whole Application.
Any thoughts how to overcome this kind of problem?
You can download a zip and unzip it client-side, but a 400 image zip might be rather large still. You could do 100 at a time, which could help.
Didn't think about putting it into a zip file. I will need to talk to the server-side developers to change it but the effort would be worth it. Thanks
I'm being lazy right now, but if you search my blog, I wrote up this process a few years back.
Ok here it is: http://www.adobe.com/devnet...
Hey Raymond, great posting. For the promises, the success callbacks are firing just fine but not the progress update. I was skeptical ngCordova wasn't firing them but then I did only one upload without promises and the progress update fired fine.
$q.all(promises).then(function(result) {
// Success!
}, function(err) {
// Error
}, function (progress) {
// constant progress updates
});
Any idea where I'm going wrong?
I think you are misunderstanding $q.all(....).then. This code is saying, "When all of the promises have successfully completed, do X". In that scenario, we aren't listening for the progress events at all. You would need to add your own listeners to *every* request and then somehow manage them in such a way that it made sense visually. Keep in mind each is getting a %, but you don't know if file 2 is bigger than file 1. It's going to be near impossible to manage imo.
Thank you, nice tutorial.
Hi! I'm a starter in Ionic. This is very helpful arc for me.
But I have a question.
In the above sample codes, how can I show the progress for downloading?
Hope your help.
In theory you can add progress event handlers to each process, but representing the status in a way that makes sense would be difficult. If you have 3 transfers and 1 is 23% done and 2 is 91% done and 3 is 5% done, what's the status? It isn't impossible, just clunky.
I think you can bind a complete handler for each one and keep a counter so you could at least say "X of Y done."
Thanks for your reply.
I hope the details.
so I think total progress. If I have 3 transfers and 1 is 23 % done and 2 is 91% done and 3 is 5% done, and then the total progress is (23 + 91 + 5) / 3 = 39 %.
Can I calculate this progress during downloading?
How about your think?
I'm not sure if that math is right, but sure, go for it. :) To monitor progress, see the FileTransfer docs. It shows how to specify a call back for it.
Thank you. Thank you.
And I have a problem. I used your code to download image files in main controller.
After in another controller, I'm trying to use the image.
But I can get the native file path, but it isn't show image tag.
How can I do? Help me!
My codes are the following.
another controller
var fname = "001.jpg";
$cordovaFile.checkFile(cordova.file.externalCacheDirectory, fname)
.then(function(obj) {
$scope.img_url = obj.nativeURL;
$scope.$apply();
}, function(error) {
alert(JSON.stringify(error));
});
I hope your help.
Best regards.
Do you see any errors in your console when you test w/ Chrome Remote Debug or Safari Remote Debug?
When I test Chrome broswer, I can't the downloads the file.
error;
XMLHttpRequest cannot load http://projects.techindusta.... No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.
So I tested it on device. But I can't see the error alert.
What's the matter?
Did you try remote debugging?
$q.all(promises) runs all promises downloading at the same time. I have more than 500 images how to make downloading in threads? Because 500+ queries to the webserver is too much and I have a lot of connection errors. Also I thing it should have some timeout between connection. Do you have any ideas how to solve my problem?
Well, there's a couple of things to consider.
First, check out the ContentSync plugin. It lets you point to a zip and the plugin handles grabbing it and extracting it. It could be perfect for this.
Another idea would be to consider 'batching' up the 500 downloads into bundles of 10-25 or so. Let the user grab some, and then grab another bunch after the first is done.
Thank you for your answer!
I separated downloading process into bundles. I wrapped $cordovaFileTransfer.download in anonymous function. And I call each download recursively.
Now i have another trouble - It works perfectly with small images (less than 30kb); But it doesn`t work with large files. How to fix it?
I`ve solved this problem by removing options, trustHosts properties
Hi, I am new to ionic framework and I tried to use your method to upload multiple images. I am now stuck with the problem that the upload result seems to be inconsistent, not all my images were uploaded, only a partial of the images were uploaded and the number varies each time I try the upload. What could be the problem?
Do you see anything interesting in your devtools console?
Cheers Ray! Great tips, just applied it to my project, cheers!
great but if there is file transfer error it's shows me multiple time how i can stop for-each on first file transfer error
So yeah - my code assumes they all work and doesn't have a catch clause for the Promise.all. If you add that, you should be able to check the results of the errors there.
Note though I would not use ngCordova w/ Ionic, but rather Ionic Native with Ionic 2+.
DOWNLOAD 20 FILES AT A TIME WITH IONIC 3 WITH ERROR HANDLING
https://jsfiddle.net/uve5e745/
Please do not post large blocks of code as comments. Next time link to a codepen or Gist.
SURE SIR....