Reputation: 106
I'm with a problem with binding an object of a Factory and a Controller and it's view.
I am trying to get the fileUri of a picture selected by the user. So far so good. The problem is that I am saving the value that file to overlays.dataUrl
. But I am referencing it on the view and it isn't updated. (I checked and the value is actually saved to the overlays.dataUrl
variable.
Here goes the source code of settings.service.js
:
(function () {
"use strict";
angular
.module("cameraApp.core")
.factory("settingsService", settingsService);
settingsService.$inject = ["$rootScope", "$cordovaFileTransfer", "$cordovaCamera"];
function settingsService($rootScope, $cordovaFileTransfer, $cordovaCamera) {
var overlays = {
dataUrl: "",
options: {
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
destinationType: Camera.DestinationType.FILE_URI
}
};
var errorMessages = [];
var service = {
overlays: overlays,
selectOverlayFile: selectOverlayFile,
errorMessages: errorMessages
};
return service;
function selectOverlayFile() {
$cordovaCamera.getPicture(overlays.options).then(successOverlay, errorOverlay);
}
//Callback functions
function successOverlay(imageUrl) {
//If user has successfully selected a file
var extension = "jpg";
var filename = getCurrentDateFileName();
$cordovaFileTransfer.download(imageUrl, cordova.file.dataDirectory + filename + '.' + extension, {}, true)
.then(function (fileEntry) {
overlays.dataUrl = fileEntry.nativeURL;
}, function (e) {
errorMessages.push(e);
});
}
function errorOverlay(message) {
//If user couldn't select a file
errorMessages.push(message);
//$rootScope.$apply();
}
}
})();
Now the controller:
(function () {
angular
.module("cameraApp.settings")
.controller("SettingsController", SettingsController);
SettingsController.$inject = ["settingsService"];
function SettingsController(settingsService) {
var vm = this;
vm.settings = settingsService;
activate();
//////////////////
function activate(){
// Nothing here yet
}
}
})();
Finnally on the view:
<h1>{{vm.settings.overlays.dataUrl}}</h1>
<button id="overlay" class="button"
ng-click="vm.settings.selectOverlayFile()">
Browse...
</button>
Whenever I change the value in the factory, it doesn't change in the view.
Thanks in advance!
Upvotes: 1
Views: 96
Reputation: 106
As pointed by Ohjay44, the factory is not updated on the view. The way to do it is using a directive (also as Ohjay44 said). To use $broadcast
, $emit
and $on
and keep the encapsulation I did what is recommended by John Papa's Angular Style Guide: created a factory (in my case a named it comms
).
Here goes the newly created directive (overlay.directive.js
):
(function () {
angular
.module('cameraApp.settings')
.directive('ptrptSettingsOverlaysInfo', settingsOverlaysInfo);
settingsOverlaysInfo.$inject = ["settingsService", "comms"];
function settingsOverlaysInfo(settingsService, comms) {
var directive = {
restrict: "EA",
templateUrl: "js/app/settings/overlays.directive.html",
link: linkFunc,
controller: "SettingsController",
controllerAs: "vm",
bindToController: true // because the scope is isolated
};
return directive;
function linkFunc(scope, element, attrs, vm) {
vm.overlays = settingsService.overlays;
comms.on("overlaysUpdate", function (event, overlays) {
vm.overlays = overlays;
});
}
}
})();
I created overlay.directive.html
with:
<div class="item item-thumbnail-left">
<img ng-src="{{vm.overlays.dataUrl}}">
<h2>{{vm.overlays.dataUrl}}</h2>
</div>
And finally I put an $emit on the settingsService
where the overlay
is updated:
(function () {
"use strict";
angular
.module("cameraApp.core")
.factory("settingsService", settingsService);
settingsService.$inject = ["comms", "$cordovaFileTransfer", "$cordovaCamera"];
function settingsService(comms, $cordovaFileTransfer, $cordovaCamera) {
var overlays = {
dataUrl: "",
options: {
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
destinationType: Camera.DestinationType.FILE_URI
}
};
var errorMessages = [];
var service = {
overlays: overlays,
selectOverlayFile: selectOverlayFile,
errorMessages: errorMessages
};
return service;
function selectOverlayFile() {
$cordovaCamera.getPicture(overlays.options).then(successOverlay, errorOverlay);
}
//Callback functions
function successOverlay(imageUrl) {
//If user has successfully selected a file
var extension = "jpg";
var filename = getCurrentDateFileName();
$cordovaFileTransfer.download(imageUrl, cordova.file.dataDirectory + filename + '.' + extension, {}, true)
.then(function (fileEntry) {
overlays.dataUrl = fileEntry.nativeURL;
// New code!!!!
comms.emit("overlaysUpdated", overlays);
}, function (e) {
errorMessages.push(e);
});
}
function errorOverlay(message) {
//If user couldn't select a file
errorMessages.push(message);
//$rootScope.$apply();
}
}
})();
I used an $emit instead of a broadcast to prevent the bubbling as explained here: What's the correct way to communicate between controllers in AngularJS?
Hope this helps someone else too.
Cheers!
Upvotes: 0
Reputation: 897
Unfortunately Factories in angularjs are not meant to be used as two way bindings. Factories and Services are only singletons. They are only there to be used when called.
Ex Factory:
app.factory('itemFactory', ['$http', '$rootScope', function($http, $rootScope) {
var service = {};
service.item = null;
service.getItem = function(id) {
$http.get(baseUrl + "getitem/" + id)
.then(function successCallback(resp) {
service.item = resp.data.Data;
$rootScope.$broadcast("itemready");
}, function errorCallback(resp) {
console.log(resp)
});
};
return service;
}]);
I use the $broadcast
so if I call getItem
my controller knows to go get the fresh data.
Ex Directive:
angular.module("itemApp").directive("item", ['itemFactory', '$routeParams', '$location', '$rootScope', '$timeout', function (itemFactory, $routeParams, $location, $rootScope, $timeout) {
return {
restrict: 'E',
templateUrl: "components/item.html",
link: function (scope, elem, attr) {
scope.item = itemFactory.item;
scope.changeMade = function(){
itemFactory.getItem(1);
}
scope.$on("itemready", function () {
scope.item = itemFactory.item;
})
}
}
}]);
So as you can see in my code above anytime I need a fresh item
I use $broadcast
and $on
to update my service and directive. I hope this makes sense, feel free to ask any questions.
Upvotes: 1