I'm trying to upload image from Camera or gallery. I wrote all the code in the service. The controller must receive the complete url from the camera or gallery to show it in a slide box, but now nothing receive.
Everything works, but I couldn't send back to the controller the url of the image. I think the error is because I'm trying to return the value before that the function "obtenerImagen" execute.
I'm trying to implement Callbacks, but I think I did not implemented correctly.
The variable that I want to return with the url of the image is urlImagen
This is my controller that calls the service:
//generar el popup para seleccionar el origen de la imagen: cámara o galería
function seleccionarImagen() {
cambiarImagenesService.seleccionarImagen()
.then(reemplazarImagen);
}
And the service:
(function() {
'use strict';
angular
.module('example.cambiarimagenes')
.factory('cambiarImagenesService', cambiarImagenesService);
cambiarImagenesService.$inject = ['remoteDataService','$q', '$ionicPopup','$cordovaCamera', '$cordovaFile', '$cordovaFileTransfer', '$cordovaDevice', '$rootScope'];
/* @ngInject */
function cambiarImagenesService(remoteDataService,$q, $ionicPopup, $cordovaCamera, $cordovaFile, $cordovaFileTransfer, $cordovaDevice, $rootScope){
var dias = [];
var mensaje = '';
var image = null;
var urlImagen = '';
var service = {
obtenerHorariosComplejo: obtenerHorariosComplejo,
seleccionarImagen: seleccionarImagen
};
return service;
//cargar una nueva imagen
function seleccionarImagen() {
var popup = seleccionarImagenPopup();
return $ionicPopup.show(popup).then(function(result) {
if (result == -1) {
return false;
}
return urlImagen;
});
}
function obtenerImagen(sourceType, callback){
var options = {
callback: callback,
quality: 100,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: sourceType,
saveToPhotoAlbum: false
};
$cordovaCamera.getPicture(options).then(function(imagePath) {
// Grab the file name of the photo in the temporary directory
var currentName = imagePath.replace(/^.*[\\\/]/, '');
//Create a new name for the photo
var d = new Date(),
n = d.getTime(),
newFileName = n + ".jpg";
// If you are trying to load image from the gallery on Android we need special treatment!
if ($cordovaDevice.getPlatform() == 'Android' && sourceType === Camera.PictureSourceType.PHOTOLIBRARY) {
window.FilePath.resolveNativePath(imagePath, function(entry) {
window.resolveLocalFileSystemURL(entry, success, fail);
function fail(e) {
console.error('Error: ', e);
}
function success(fileEntry) {
var namePath = fileEntry.nativeURL.substr(0, fileEntry.nativeURL.lastIndexOf('/') + 1);
// Only copy because of access rights
$cordovaFile.copyFile(namePath, fileEntry.name, cordova.file.dataDirectory, newFileName).then(function(success){
image = cordova.file.dataDirectory + newFileName;
urlImagen = image;
}, function(error){
$scope.showAlert('Error', error.exception);
});
};
}
);
} else {
var namePath = imagePath.substr(0, imagePath.lastIndexOf('/') + 1);
// Move the file to permanent storage
$cordovaFile.moveFile(namePath, currentName, cordova.file.dataDirectory, newFileName).then(function(success){
image = cordova.file.dataDirectory + newFileName;
urlImagen = image;
}, function(error){
$scope.showAlert('Error', error.exception);
});
}
},
function(err){
console.log("error en el serivicio o cancelacion:"+err);
// Not always an error, maybe cancel was pressed...
})
}
//poopup para cargar nuevo imagen
function seleccionarImagenPopup() {
var scope = $rootScope.$new();
scope.data = {
tipo: null
};
return {
templateUrl: 'scripts/complejo/agenda/nuevo-turno.html',
title: "¿De dónde desea obtener la imagen?",
scope: scope,
buttons: [{
text: 'Cancelar',
onTap: function(e) {
scope.tipo = -1
return scope.tipo;
}
}, {
text: '<b>Cámara</b>',
type: 'button-positive',
onTap: function(e) {
scope.tipo = Camera.PictureSourceType.CAMERA;
obtenerImagen(scope.tipo, function(val){
urlImagen = val;
});
console.log("el valor de la imagen al tocar la camara es:"+image);
return urlImagen;
}
}, {
text: '<b>Galería</b>',
type: 'button-positive',
onTap: function(e) {
scope.tipo = Camera.PictureSourceType.PHOTOLIBRARY;
obtenerImagen(scope.tipo, function(val){
urlImagen = val;
});
console.log("el valor de la imagen al tocar la galeria es:"+image);
return urlImagen;
}
}]
};
}
//generar error si hubo un problema
function generarError(e){
console.log("error!!!!!!:"+e);
if (e.message) {
return $q.reject(e.message);
}
return $q.reject('Ups! Hubo un problema al conectarse al servidor.');
}
}
})();
Thanks for helping me!
//EDIT//
This is now my service:
(function() {
'use strict';
angular
.module('example.cambiarimagenes')
.factory('cambiarImagenesService', cambiarImagenesService);
cambiarImagenesService.$inject = ['remoteDataService','$q', '$ionicPopup','$cordovaCamera', '$cordovaFile', '$cordovaFileTransfer', '$cordovaDevice', '$rootScope'];
/* @ngInject */
function cambiarImagenesService(remoteDataService,$q, $ionicPopup,$cordovaCamera, $cordovaFile, $cordovaFileTransfer, $cordovaDevice, $rootScope){
var dias = [];
var mensaje = '';
var image = null;
var urlImagen = '';
var service = {
obtenerHorariosComplejo: obtenerHorariosComplejo,
seleccionarImagen: seleccionarImagen
};
return service;
//cargar una nueva imagen
function seleccionarImagen() {
var popup = seleccionarImagenPopup();
return $ionicPopup.show(popup).then(function(result) {
if (result == -1) {
return false;
}
return urlImagen;
});
}
function obtenerImagen(sourceType){
var options = {
quality: 100,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: sourceType,
saveToPhotoAlbum: false
};
return $cordovaCamera.getPicture(options).then(function(imagePath) {
// Grab the file name of the photo in the temporary directory
var currentName = imagePath.replace(/^.*[\\\/]/, '');
//Create a new name for the photo
var d = new Date(),
n = d.getTime(),
newFileName = n + ".jpg";
// If you are trying to load image from the gallery on Android we need special treatment!
if ($cordovaDevice.getPlatform() == 'Android' && sourceType === Camera.PictureSourceType.PHOTOLIBRARY) {
window.FilePath.resolveNativePath(imagePath, function(entry) {
window.resolveLocalFileSystemURL(entry, success, fail);
function fail(e) {
console.error('Error: ', e);
}
function success(fileEntry) {
var namePath = fileEntry.nativeURL.substr(0, fileEntry.nativeURL.lastIndexOf('/') + 1);
// Only copy because of access rights
$cordovaFile.copyFile(namePath, fileEntry.name, cordova.file.dataDirectory, newFileName).then(function(success){
image = cordova.file.dataDirectory + newFileName;
return image;
}, function(error){
$scope.showAlert('Error', error.exception);
});
};
}
);
} else {
var namePath = imagePath.substr(0, imagePath.lastIndexOf('/') + 1);
// Move the file to permanent storage
$cordovaFile.moveFile(namePath, currentName, cordova.file.dataDirectory, newFileName).then(function(success){
image = cordova.file.dataDirectory + newFileName;
return image;
}, function(error){
$scope.showAlert('Error', error.exception);
});
}
},
function(err){
console.log("error en el serivicio o cancelacion:"+err);
// Not always an error, maybe cancel was pressed...
})
}
//poopup para cargar nuevo imagen
function seleccionarImagenPopup() {
var scope = $rootScope.$new();
scope.data = {
tipo: null
};
return {
templateUrl: 'scripts/complejo/agenda/nuevo-turno.html',
title: "¿De dónde desea obtener la imagen?",
scope: scope,
buttons: [{
text: 'Cancelar',
onTap: function(e) {
scope.tipo = -1
return scope.tipo;
}
}, {
text: '<b>Cámara</b>',
type: 'button-positive',
onTap: function(e) {
scope.tipo = Camera.PictureSourceType.CAMERA;
var promise = obtenerImagen(scope.tipo)
.then(function(val){
// asignamos el valor asincrónico
urlImagen = val;
// retornamos el valor a la cadena
return val;
});
// retornamos la promesa de manera síncrona
return promise;
}
}, {
text: '<b>Galería</b>',
type: 'button-positive',
onTap: function(e) {
scope.tipo = Camera.PictureSourceType.PHOTOLIBRARY;
var promise = obtenerImagen(scope.tipo)
.then(function(val){
// asignamos el valor asincrónico
urlImagen = val;
// retornamos el valor a la cadena
return val;
});
// retornamos la promesa de manera síncrona
return promise;
}
}]
};
}
//generar error si hubo un problema
function generarError(e){
console.log("error!!!!!!:"+e);
if (e.message) {
return $q.reject(e.message);
}
return $q.reject('Ups! Hubo un problema al conectarse al servidor.');
}
}
})();
The problem is that the callback function is executed asynchronously.
//ERRONEOUS
onTap: function(e) {
scope.tipo = Camera.PictureSourceType.CAMERA;
obtenerImagen(scope.tipo, function(val){
//ASYNCHRONOUSLY ASSIGNED
urlImagen = val;
});
console.log("el valor de la imagen al tocar la camara es:"+image);
//SYNCHRONOUSLY RETURNED
return urlImagen;
}
The value is returned before the value is assigned. Subsequent code executes before the value is defined.
The obtenerImagen
function needs to be refactored to return a promise and the promise needs to be returned.
//GOOD
onTap: function(e) {
scope.tipo = Camera.PictureSourceType.CAMERA;
var promise = obtenerImagenPromise(scope.tip)
.then(function(val){
//ASYNCHRONOUSLY ASSIGNED
urlImagen = val;
//return value to chain
return val;
});
//SYNCHRONOUSLY RETURN PENDING PROMISE
return promise;
}
By returning a promise, subsequent code can use the .then
method of the promise to delay execution until the value is defined.
Because calling the .then
method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
-- AngularJS $q Service API Reference - Chaining Promises
Also see SO: Why are Callbacks from Promise .then
Methods an Anti-Pattern
what changes shall I need to do in "obtenerImagenPromise" to returns the promise correctly? Because now I have the following error "TypeError: Cannot read property 'then' of undefined" I think I need to return image in the function "obtenerImagenPromise"
Start with returning the derived promise from $cordovaCamera.getPicture
:
//function obtenerImagen(sourceType, callback){
function obtenerImagenPromise(sourceType){
var options = {
//callback: callback,
quality: 100,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: sourceType,
saveToPhotoAlbum: false
};
//$cordovaCamera.getPicture(options).then(function(imagePath) {
return $cordovaCamera.getPicture(options).then(function(imagePath) {
//^^^^^^ ---- return derived promise
// Grab the file name of the photo in the temporary directory
The .then
method always returns a new derived promise. That promise needs to be returned to the parent function. Also make sure that the functions inside the .then
method return a value or promise. Failure to return something will result in the promise resolving as undefined
.
I set the returns but always the controller receive
undefined
Debugging hint: Put console.log
statements to see intermediate values:
//$cordovaCamera.getPicture(options).then(function(imagePath) {
return $cordovaCamera.getPicture(options)
//^^^^^^ ---- return derived promise
.then(
function(imagePath) {
//ADD console.log to see intermediate data
console.log("getPicture success handler called");
console.log("imagePath= "+imagePath);
// Grab the file name of the photo in the temporary directory
var currentName = imagePath.replace(/^.*[\\\/]/, '');
//...
//Always return something
return "something";
},
function(err){
console.log("error en el serivicio o cancelacion:"+err);
// Not always an error, maybe cancel was pressed...
//throw to chain error
throw "error en el serivicio o cancelacion:"+err
}
);
};
Also make sure that the functions inside the .then
method return a value or promise. Failure to return something will result in the promise resolving as undefined
.
The rule of thumb with functional programming is -- always return something.