Ionic 3 Photo Library Upload to Server
If your app is working with images and you need to handle both local files and upload files things can get a bit catchy with Ionic. Peculiarly as debugging the filesystem and paths is cumbersome, information technology'southward not automatically clear how everything needs to piece of work.
This Ionic 4 images guide aims to clarify how yous can:
- Capture images using camera or photo library
- Store image files inside your Ionic 4 app
- Upload files from their local path using a POST request
The outcome volition be a uncomplicated app like you can see below.
For this tutorial I apply Ionic iv so perhaps some details might slightly change over the next time!
Starting our Ionic iv Prototype Upload App
To get started we create a blank new Ionic four app and install all the plugins we demand.
Of course we need the camera and file plugin, but we too need the new Webview package for a reason we will see later. For at present get ahead and run:
ionic get-go devdacticImages blank -- type = angular cd devdacticImages # Ionic Native Packages npm i @ ionic - native / photographic camera npm i @ ionic - native / file npm i @ ionic - native / ionic - webview npm i @ ionic - native / file - path # Cordova Packages ionic cordova plugin add cordova - plugin - camera ionic cordova plugin add cordova - plugin - file ionic cordova plugin add together cordova - plugin - ionic - webview ionic cordova plugin add cordova - sqlite - storage ionic cordova plugin add cordova - plugin - filepath |
At present we demand to hook up everything inside our module so we can use the plugins later. We likewise make utilise of the Ionic Storage non to store the files but the path to a file later on on.
Go ahead and change your src/app/app.module.ts to:
ane ii three 4 5 6 7 8 9 10 xi 12 13 14 15 16 17 xviii 19 20 21 22 23 24 25 26 27 28 29 thirty 31 32 33 34 35 36 37 38 39 | import { NgModule } from '@angular/core' ; import { BrowserModule } from '@angular/platform-browser' ; import { RouterModule , RouteReuseStrategy , Routes } from '@angular/router' ; import { IonicModule , IonicRouteStrategy } from '@ionic/angular' ; import { SplashScreen } from '@ionic-native/splash-screen/ngx' ; import { StatusBar } from '@ionic-native/condition-bar/ngx' ; import { AppComponent } from './app.component' ; import { AppRoutingModule } from './app-routing.module' ; import { HttpClientModule } from '@angular/common/http' ; import { Camera } from '@ionic-native/Camera/ngx' ; import { File } from '@ionic-native/File/ngx' ; import { WebView } from '@ionic-native/ionic-webview/ngx' ; import { FilePath } from '@ionic-native/file-path/ngx' ; import { IonicStorageModule } from '@ionic/storage' ; @ NgModule ( { declarations : [ AppComponent ] , entryComponents : [ ] , imports : [ BrowserModule , IonicModule . forRoot ( ) , AppRoutingModule , HttpClientModule , IonicStorageModule . forRoot ( ) ] , providers : [ StatusBar , SplashScreen , { provide : RouteReuseStrategy , useClass : IonicRouteStrategy } , Camera , File , WebView , FilePath ] , bootstrap : [ AppComponent ] } ) export class AppModule { } |
We don't need whatever special routing so our paradigm upload app is prepared for some action!
Fix the WkWebView Plugin
At the time writing this tutorial there was also a bug within the webview plugin for iOS which leads to an app crash. Yous can see the details in this issue which might already be fixed later.
UPDATE: The issue is airtight, so you shouldn't get into whatever problem.
If y'all still encounter issues, you tin can open your platforms/ios/devdacticImages/Plugins/cordova-plugin-ionic-webview/CDVWKWebViewEngine.chiliad and supersede information technology with the contents of the fixed file.
The View for Our Epitome Upload & Direction App
Let's offset with the easiest office which is the view in our Ionic 4 image upload app. We need to display a button so users tin select am image to upload. This will trigger an action sheet, and once the user has finished the image dialog a new paradigm will be displayed inside the list.
The list itself shows all of our locally stored files (more on the logic afterward) with a button to upload the image and to delete the file and all references.
At that place isn't really much virtually this so get-go of all now change your src/app/domicile/home.page.html to this:
1 ii 3 four 5 6 7 8 9 ten 11 12 13 xiv xv sixteen 17 eighteen xix 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | < ion - header > < ion - toolbar colour = "primary" > < ion - title > Ionic Image Upload < / ion - title > < / ion - toolbar > < / ion - header > < ion - content padding > < h3 * ngIf = "images.length == 0" text - center > Please Select Epitome ! < / h3 > < ion - list > < ion - detail * ngFor = "let img of images; index as pos" text - wrap > < ion - thumbnail slot = "start" > < ion - img [ src ] = "img.path" > < / ion - img > < / ion - thumbnail > < ion - label > { { img . proper name } } < / ion - label > < ion - button slot = "terminate" fill = "articulate" ( click ) = "startUpload(img)" > < ion - icon slot = "icon-merely" proper name = "cloud-upload" > < / ion - icon > < / ion - push > < ion - button slot = "stop" fill up = "clear" ( click ) = "deleteImage(img, pos)" > < ion - icon slot = "icon-only" name = "trash" > < / ion - icon > < / ion - button > < / ion - item > < / ion - listing > < / ion - content > < ion - footer > < ion - toolbar colour = "primary" > < ion - push button fill = "clear" expand = "total" color = "light" ( click ) = "selectImage()" > < ion - icon slot = "start" proper noun = "camera" > < / ion - icon > Select Epitome < / ion - push > < / ion - toolbar > < / ion - footer > |
The Basics for Our Image Upload
Let's dive into the real activeness. I'll split up the code for the class in multiple sections so we can go over them one by ane. To start, let'due south add together all the dependencies our form needs (which are quite a few) and the first function that volition be called once the app starts.
In the beginning we volition look inside our storage to see if we accept any stored information virtually images already captured. This array will but contain the proper name of a file like "1234.png", so for each entry nosotros need to resolve the name to the local path of our app which nosotros add to the object every bit filePath
.
To display the prototype we need another path, and here we can make use of the webview.convertFileSr()
which resolves a file:// path to a path that the WebView understands. All of this information goes to the local assortment which our previous view can then iterate.
Now make the kickoff changes inside your src/app/home/home.page.ts to get started:
1 ii 3 iv 5 six 7 8 9 10 eleven 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 twoscore 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 lx 61 62 63 64 65 66 67 68 | import { Component , OnInit , ChangeDetectorRef } from '@angular/core' ; import { Photographic camera , CameraOptions , PictureSourceType } from '@ionic-native/Camera/ngx' ; import { ActionSheetController , ToastController , Platform , LoadingController } from '@ionic/athwart' ; import { File , FileEntry } from '@ionic-native/File/ngx' ; import { HttpClient } from '@angular/common/http' ; import { WebView } from '@ionic-native/ionic-webview/ngx' ; import { Storage } from '@ionic/storage' ; import { FilePath } from '@ionic-native/file-path/ngx' ; import { finalize } from 'rxjs/operators' ; const STORAGE_KEY = 'my_images' ; @ Component ( { selector : 'app-home' , templateUrl : 'home.page.html' , styleUrls : [ 'home.page.scss' ] , } ) consign course HomePage implements OnInit { images = [ ] ; constructor ( private camera : Camera , private file : File , private http : HttpClient , private webview : WebView , private actionSheetController : ActionSheetController , private toastController : ToastController , individual storage : Storage , private plt : Platform , individual loadingController : LoadingController , individual ref : ChangeDetectorRef , individual filePath : FilePath ) { } ngOnInit ( ) { this . plt . set ( ) . and so ( ( ) = > { this . loadStoredImages ( ) ; } ) ; } loadStoredImages ( ) { this . storage . get ( STORAGE_KEY ) . then ( images = > { if ( images ) { allow arr = JSON . parse ( images ) ; this . images = [ ] ; for ( let img of arr ) { let filePath = this . file . dataDirectory + img ; let resPath = this . pathForImage ( filePath ) ; this . images . push ( { name : img , path : resPath , filePath : filePath } ) ; } } } ) ; } pathForImage ( img ) { if ( img === null ) { return '' ; } else { permit converted = this . webview . convertFileSrc ( img ) ; return converted ; } } async presentToast ( text ) { const toast = wait this . toastController . create ( { message : text , position : 'bottom' , duration : 3000 } ) ; toast . present ( ) ; } // Next functions follow here... } |
Calculation New Images
The next pace is to add new images. Nosotros start this process by displaying an action sheet from which the user can either select the photographic camera or photo library as a source.
Once the source is defined we employ the camera like e'er and we are not using a base64 as a result only the real FILE_URI of the image. Otherwise nosotros would accept to store those super large strings within the storage which is not really considered best do.
Later on the image was selected we want to copy the file over to our apps data directory with a new proper noun and then we are not more dependent on where the file really exists as we have our own copy.
Get ahead and add together the 2 functions below what you already got:
1 2 3 iv v half dozen vii 8 9 ten 11 12 13 14 15 sixteen 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 twoscore 41 42 43 44 45 46 47 48 | async selectImage ( ) { const actionSheet = expect this . actionSheetController . create ( { header : "Select Prototype source" , buttons : [ { text : 'Load from Library' , handler : ( ) = > { this . takePicture ( this . camera . PictureSourceType . PHOTOLIBRARY ) ; } } , { text : 'Apply Photographic camera' , handler : ( ) = > { this . takePicture ( this . camera . PictureSourceType . CAMERA ) ; } } , { text : 'Abolish' , function : 'abolish' } ] } ) ; expect actionSheet . nowadays ( ) ; } takePicture ( sourceType : PictureSourceType ) { var options : CameraOptions = { quality : 100 , sourceType : sourceType , saveToPhotoAlbum : fake , correctOrientation : true } ; this . photographic camera . getPicture ( options ) . then ( imagePath = > { if ( this . platform . is ( 'android' ) && sourceType === this . camera . PictureSourceType . PHOTOLIBRARY ) { this . filePath . resolveNativePath ( imagePath ) . then ( filePath = > { allow correctPath = filePath . substr ( 0 , filePath . lastIndexOf ( '/' ) + ane ) ; let currentName = imagePath . substring ( imagePath . lastIndexOf ( '/' ) + ane , imagePath . lastIndexOf ( '?' ) ) ; this . copyFileToLocalDir ( correctPath , currentName , this . createFileName ( ) ) ; } ) ; } else { var currentName = imagePath . substr ( imagePath . lastIndexOf ( '/' ) + 1 ) ; var correctPath = imagePath . substr ( 0 , imagePath . lastIndexOf ( '/' ) + one ) ; this . copyFileToLocalDir ( correctPath , currentName , this . createFileName ( ) ) ; } } ) ; } |
Copy Files & Store Local Reference
So we got the image file and want to copy it to our app. For the copy office we need the path to the original file, the name, the new path and the new proper name.
This function volition then copy over the original file under the new name to our data directory.
Now it's non actually a skilful thought to list only the files inside our app to continue rails of them, as y'all might perhaps likewise need to shop boosted data.
Therefore, we update our Storage and store the information with the JSON.stringify()
as an array back. Also, we create one additional object that we add to our local array with the co-ordinate data and resolved paths similar nosotros besides practise when our app starts.
Hither's the logic to copy the file and store the information:
1 2 three four v half-dozen vii eight ix ten eleven 12 thirteen 14 xv 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | createFileName ( ) { var d = new Date ( ) , north = d . getTime ( ) , newFileName = n + ".jpg" ; render newFileName ; } copyFileToLocalDir ( namePath , currentName , newFileName ) { this . file . copyFile ( namePath , currentName , this . file . dataDirectory , newFileName ) . then ( success = > { this . updateStoredImages ( newFileName ) ; } , mistake = > { this . presentToast ( 'Error while storing file.' ) ; } ) ; } updateStoredImages ( name ) { this . storage . become ( STORAGE_KEY ) . so ( images = > { let arr = JSON . parse ( images ) ; if ( ! arr ) { let newImages = [ name ] ; this . storage . set up ( STORAGE_KEY , JSON . stringify ( newImages ) ) ; } else { arr . push ( name ) ; this . storage . set ( STORAGE_KEY , JSON . stringify ( arr ) ) ; } let filePath = this . file . dataDirectory + name ; let resPath = this . pathForImage ( filePath ) ; allow newEntry = { name : name , path : resPath , filePath : filePath } ; this . images = [ newEntry , . . . this . images ] ; this . ref . detectChanges ( ) ; // trigger change detection wheel } ) ; } |
Delete Local Files
We got almost all important parts in place, just if we want to terminate this example we also need the delete function. At this point you need to take care of 3 elements:
- Remove the object from our local
images
assortment - Remove the item from the Ionic Storage
- Remove the actual file from our app binder
But don't worry, all of those actions can be performed with merely i simple role:
deleteImage ( imgEntry , position ) { this . images . splice ( position , 1 ) ; this . storage . get ( STORAGE_KEY ) . then ( images = > { let arr = JSON . parse ( images ) ; permit filtered = arr . filter ( name = > proper noun != imgEntry . name ) ; this . storage . set ( STORAGE_KEY , JSON . stringify ( filtered ) ) ; var correctPath = imgEntry . filePath . substr ( 0 , imgEntry . filePath . lastIndexOf ( '/' ) + 1 ) ; this . file . removeFile ( correctPath , imgEntry . name ) . and then ( res = > { this . presentToast ( 'File removed.' ) ; } ) ; } ) ; } |
Upload Files with Postal service and Form Data
Nosotros got files, we got the storage, nosotros got all information we need but nevertheless the upload process for files isn't that easy. In our instance nosotros will take a PHP backend that accepts our file (we'll build it in the terminal pace) and we need to mail it somehow. Also, we don't need to use the old file transfer plugin anymore, all of this also works with the full general POST of Angular HTTP.
Some of the following logic for reading the local file comes from this great tutorial.
The idea is that we first need to resolve the path of our local file which results in a FileEntry
object. On this object we can than call the file()
function and utilise a FileReader
to read in the data of a file object.
The consequence is a hulk the we can add as FormData
to our request which is in the end a standard HTTP POST call with the class data.
Now go alee and add the last functions to your class:
1 2 3 4 v six vii 8 9 10 11 12 13 xiv 15 16 17 xviii 19 20 21 22 23 24 25 26 27 28 29 thirty 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // Inspired past https://golb.hplar.ch/2017/02/Uploading-pictures-from-Ionic-ii-to-Jump-Boot.html startUpload ( imgEntry ) { this . file . resolveLocalFilesystemUrl ( imgEntry . filePath ) . and so ( entry = > { ( < FileEntry > entry ) . file ( file = > this . readFile ( file ) ) } ) . grab ( err = > { this . presentToast ( 'Error while reading file.' ) ; } ) ; } readFile ( file : whatsoever ) { const reader = new FileReader ( ) ; reader . onload = ( ) = > { const formData = new FormData ( ) ; const imgBlob = new Blob ( [ reader . result ] , { blazon : file . type } ) ; formData . append ( 'file' , imgBlob , file . name ) ; this . uploadImageData ( formData ) ; } ; reader . readAsArrayBuffer ( file ) ; } async uploadImageData ( formData : FormData ) { const loading = await this . loadingController . create ( { message : 'Uploading image...' , } ) ; await loading . present ( ) ; this . http . post ( "http://localhost:8888/upload.php" , formData ) . pipe ( finalize ( ( ) = > { loading . dismiss ( ) ; } ) ) . subscribe ( res = > { if ( res [ 'success' ] ) { this . presentToast ( 'File upload consummate.' ) } else { this . presentToast ( 'File upload failed.' ) } } ) ; } |
That'due south all for the Ionic paradigm upload app, the app will work now besides the upload part but yous can run information technology already now.
Make sure that yous are running the app on the simulator/device equally we use the photographic camera and filesystem and then information technology volition only work where Cordova is available!
The PHP Upload Logic
At present I'm not a PHP expert so I'll brand this as quick equally possible.
If you accept a server you can use that 1, otherwise I simply recommend to download XAMPP and install it local.
I'm not going to cover that process since this is virtually Ionic epitome upload and non how to configure PHP. If you take prepare information technology up, you can showtime of all create a upload.php to accept uploads:
1 2 3 4 5 vi seven 8 ix 10 11 12 13 fourteen 15 sixteen 17 | <?php header ( 'Admission-Command-Permit-Origin: *' ) ; $target_path = "uploads/" ; $target_path = $target_path . basename ( $_FILES [ 'file' ] [ 'name' ] ) ; if ( move_uploaded_file ( $_FILES [ 'file' ] [ 'tmp_name' ] , $target_path ) ) { header ( 'Content-type: awarding/json' ) ; $data = [ 'success' = > true , 'message' = > 'Upload and move success' ] ; repeat json_encode ( $data ) ; } else { header ( 'Content-blazon: application/json' ) ; $information = [ 'success' = > false , 'message' = > 'In that location was an fault uploading the file, delight try again!' ] ; echo json_encode ( $data ) ; } ?> |
As well, make sure to create a uploads folder next to this file, as it will re-create the images into that binder.
Additionally, to see the results of our hard work, I created a niggling HTML file that will scan the uploads binder and show them so we can directly come across if our upload worked, create this as index.php next to the previous file and insert:
1 2 3 4 5 half dozen 7 8 9 10 xi 12 13 14 15 16 17 xviii 19 twenty 21 22 | < ! DOCTYPE html > < html > < caput > < meta charset = "utf-eight" > < meta proper noun = "viewport" content = "initial-calibration=1, maximum-calibration=1, user-scalable=no, width=device-width" > < title > Devdactic Image Upload < / championship > < / head > < body > < h1 > Ionic Image Upload < / h1 > <?php $scan = scandir ( 'uploads' ) ; foreach ( $scan as $file ) { if ( ! is_dir ( $file ) ) { repeat '<h3>' . $file . '</h3>' ; repeat '<img src="uploads/' . $file . '" style="width: 400px;"/><br />' ; } } ?> < / torso > < / html > |
Yous can now beginning your local MAMP server and navigate to http://localhost:8888 which volition display your Ionic Images overview.
In our instance nosotros used this URL for the upload, but this will just work if yous run the app on the simulator. If yous deploy the app to your iOS device you need to change the URL to the IP of your reckoner!
Decision
The process of capturing prototype with Ionic 4 is still the aforementioned, only storing local files and uploading works actually a bit easier and so with previous versions.
If you don't see your files it's almost likely an issue with the WKWebView and how local files can be displayed, then if y'all come across any bug delight provide some farther logs!
You can besides notice a video version of this article below.
Source: https://devdactic.com/ionic-4-image-upload-storage/