NGHide Not Effected When Value Changed in Directive - angularjs

I have a pair of elements that I want to hide or show depending on if photo data is available. On the initial page load this works fine, however, if a photo is added via the capturePhoto method in the directive I have to reload the page to have the elements show/hide as they should.
.html
<button ng-hide="measurement.photo != ''" class="btn btn-default" ng-click="capturePhoto(measurement)">Photo</button>
<img ng-show="measurement.photo != ''" id="{{measurement.__id}}" src="{{measurement.photo}}" width="100" height="100"/>
.js
.directive( 'sidingMeasurementGroup', function( $spawn, $measurementGroups, $compile, $projects, $filter, $photo )
{
return {
scope:
{
group: "=",
measurementTarget: "="
},
restrict: 'E', // E = Element, A = Attribute, C = Class, M = Comment
templateUrl: 'partials/directives/siding-measurement-group.html',
transclude: true,
replace:true,
link: function( $scope, iElm, iAttrs, controller )
{
$scope.capturePhoto = function(measurement)
{
function fail( e )
{
$error.
throw ( 'problem taking photo', e );
}
function success( imageURI )
{
cordova.exec(
function callback(data) {
measurement.photo = data.filePath;
var image = document.getElementById(measurement.__id);
image.src = null;
image.src = measurement.photo;
},
function errorHandler(err) {
alert('Error');
},
'CopyFiles',
'copyFileFromTmp',
[ imageURI ]
);
}
$photo.takePhoto( success, fail );
}
}
};
} )
If I log the value of measurement.photo after setting it, the correct value is shown, but on the HTML side the elements don't change their visibility. How do I make them react to the change that took place in my directive?

have you tried scope.$apply();
Edit to add:
This worked perfectly, this is my resulting javascript code using scope.$apply()
.directive( 'sidingMeasurementGroup', function( $spawn, $measurementGroups, $compile, $projects, $filter, $photo )
{
return {
scope:
{
group: "=",
measurementTarget: "="
},
restrict: 'E', // E = Element, A = Attribute, C = Class, M = Comment
templateUrl: 'partials/directives/siding-measurement-group.html',
transclude: true,
replace:true,
link: function( $scope, iElm, iAttrs, controller )
{
$scope.capturePhoto = function(measurement)
{
function fail( e )
{
$error.
throw ( 'problem taking photo', e );
}
function success( imageURI )
{
cordova.exec(
function callback(data) {
$scope.$apply(function () {
$scope.photoTaken = true;
measurement.photo = data.filePath;
var image = document.getElementById(measurement.__id);
image.src = null;
image.src = measurement.photo;
});
},
function errorHandler(err) {
alert('Error');
},
'CopyFiles',
'copyFileFromTmp',
[ imageURI ]
);
}
$photo.takePhoto( success, fail );
}

Related

How to redisplay directive after Javascript function executes

I have an AngularJS Directive defined in a Javascript file that looks like this:
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
var directive = {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope: true
};
return directive;
function link(scope, element, attrs) {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
}
}
})();
and in the template URL of the directive there is a button that calls a Javascript function which looks like this:
function upload(files) {
var formData = new FormData();
angular.forEach(files, function (file) {
formData.append(file.name, file);
});
return fileManagerClient.save(formData)
.$promise
.then(function (result) {
if (result && result.files) {
result.files.forEach(function (file) {
if (!fileExists(file.name)) {
service.files.push(file);
}
});
}
appInfo.setInfo({ message: "files uploaded successfully" });
return result.$promise;
},
function (result) {
appInfo.setInfo({ message: "something went wrong: " +
result.data.message });
return $q.reject(result);
})
['finally'](
function () {
appInfo.setInfo({ busy: false });
service.status.uploading = false;
});
}
Once I select files for upload and click the upload button I need to reload the directive or somehow get it back to it's initial state so I can upload additional files. I'm relatively new to AngularJS and I'm not quite sure how to do this. Any help is much appreciated.
Thanks,
Pete
You just need to create a reset method. Also, you may want to call the parent controller function.
Using answer from this
ngFileSelect.directive.js
...
.directive("onFileChange",function(){
return {
restrict: 'A',
link: function($scope,el){
var onChangeHandler = scope.$eval(attrs.onFileChange);
el.bind('change', onChangeHandler);
}
}
...
fileUploader.directive.js
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
return {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope:{
onSubmitCallback: '&',
onFileChange: '&'
}
};
function link(scope, element, attrs) {
scope.reset = reset;
scope.fileChange = fileChange;
reset();
function reset() {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
if(typeof scope.onSubmitCallback === 'function') {
scope.onSubmitCallback();
}
}
function fileChange(file) {
if(typeof scope.onFileChange === 'function'){
scope.onFileChange(file);
}
}
}
}
})();
UploadFile.html
<form>
<div>
...
</div>
<input type="submit" ng-click="reset()" file-on-change="fileChange($files)" />Upload
</form>
parent.html
<file-uploader on-submit-callback="onUpload" on-file-change="onFileChange" ng-controller="UploadCtrl" />
upload.controller.js
...
$scope.onUpload = function() {
console.log('onUpload clicked %o', arguments);
};
$scope.onFileChange = function(e) {
var imageFile = (e.srcElement || e.target).files[0];
}
...

angularjs: how to know all directive is ready

I want make sure all directives are ready. How to? Here is what I try, but not correct.
leafUi.factory('leafState', function($rootScope) {
var unreadyUi = [], readyUi = [];
return {
unready: function(ui) {
console.log(ui + ' unready');
unreadyUi.push(ui);
},
ready: function(ui) {
console.warn(ui + ' ready');
readyUi.push(ui);
if (readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
}
}
});
leafUi.directive('leafScroll', function($timeout, leafState, leafScroll) {
return {
restrict: 'E',
transclude: true,
link: function(scope, ele, attrs, ctrl, transclude) {
leafState.unready('leafScroll');
// more code .....
leafState.ready('leafScroll');
}
};
});
leafUi.directive('leafContent', function($timeout, leafState, leafScroll) {
return {
restrict: 'E',
transclude: true,
link: function(scope, ele, attrs, ctrl, transclude) {
leafState.unready('leafContent');
// more code .....
leafState.ready('leafContent');
}
};
});
// more directive.....
Here is an example log:
From the log, we can know that directive ready and unready can be separated by other directive and length of readyUi has many chance to equal to unreadyUi.
So how can I assure all directives are ready?
Perhaps add another condition to what you define as leafUiReady could help you. Since the directives are loaded almost synchronously you get the logic that your undreadyUi and readyUi are almost always the same length during compilation.
Perhaps add logic like this to your leafState factory where you also look at the DOM ready-function.
leafUi.factory('leafState', function($rootScope) {
var unreadyUi = [], readyUi = [];
var domReady = false;
angular.element(document).ready(function () {
domReady = true;
if (readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
});
return {
unready: function(ui) {
console.log(ui + ' unready');
unreadyUi.push(ui);
},
ready: function(ui) {
console.warn(ui + ' ready');
readyUi.push(ui);
if (domReady && readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
}
}
});
Perhaps change some other logic to make this code a bit cleaner.

angularjs directive 2 way binding not working

Here is my directive, it's simple task is to Locale Date String time:
.directive('localeDateString',['$window', function ($window) {
return {
restrict: 'E',
replace: true,
scope: {
time: '='
},
template: '<span>{{timeLocal}}</span>',
link: function ($scope, $element) {
if ($scope.time != null) {
profileDate = new Date($scope.time);
var cultureCode = $window.ApiData.CultureCode;
$scope.timeLocal = profileDate.toLocaleDateString(cultureCode);
}
}
};
}])
Usage in HTML:
<li ng-repeat="note in profile.AccountProfile.Notes" class="noteItem">
<locale-date-string time="note.Created" ></locale-date-string>
<span>{{note.UserName}}</span>
<!-- other stuff .. -->
</li>
When I'm loading the object "profile" from JSON everything is OK
the problem is when i change "note.Created" from controller - the directive seem not to work(other members of Note are updating ok):
In the controller:
DataService.updateProfileRemark(objRemark)
.then(function (response) {
// all is ok;
var profileIndex = $scope.ProfileList.indexOf(profile);
var noteIndex = $scope.ProfileList[profileIndex].AccountProfile.Notes.indexOf(note);
// this is working:
$scope.ProfileList[profileIndex].AccountProfile.Notes[noteIndex].UserName = objRemark.UserName;
// this is not:
$scope.ProfileList[profileIndex].AccountProfile.Notes[noteIndex].Created = Date.now();
},
function (errResponse) {
// handle err
}
);
For example, here is the scope before "updateProfileRemark":
and after:
Why the 2 way binding not working?
Thanks.
link is only executed once. If you want to setup two-way binding between $scope.timeLocal and $scope.time, setup a $watch:
link: function ($scope, $element) {
$scope.$watch('time', function(newTime) {
if (newTime != null) {
var profileDate = new Date(newTime);
var cultureCode = $window.ApiData.CultureCode;
$scope.timeLocal = profileDate.toLocaleDateString(cultureCode);
}
});

How to call function in directive from button click

How do I call a function in a directive from a button click? I have been trying and have come up with this (but it is not working):
HTML
<div ng-controller="myMapCTRL as myMapctrl">
<div id="panel">
<input ng-click="updateMap()" type=button value="Remove Path">
</div>
<my-map-with-path id="map-canvas" class="map-canvas" ng-if="dataHasLoaded" ></my-map-with-path>
</div>
Controller
app.controller('myMapCTRL', ['$scope', 'PathService', function($scope, PathService){
//console.log('in controller');
$scope.removed = false;
if(typeof $scope.paths ==='undefined') {
$scope.dataHasLoaded = false;
$scope.center = new google.maps.LatLng(51.5130300, -0.3202410);
PathService.getPaths().then(function(data){
$scope.paths = data;
$scope.dataHasLoaded = true;
//console.log('paths loaded');
});
};
}]);
Directive
app.directive('myMapWithPath', [function() {
return{
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: 'myMapCTRL',
link: function(scope, element, attrs){
//console.log('in link');
scope.updateMap = function() {
console.log('inside updateMap()');
}
var map, path = new google.maps.MVCArray(),
service = new google.maps.DirectionsService(), poly;
//var center = new google.maps.LatLng(51.5130300, -0.3202410);
var myOptions = {
zoom: 15,
center: scope.center,
mapTypeId: google.maps.MapTypeId.ROADMAP,
mapTypeControlOptions: {
mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.HYBRID,
google.maps.MapTypeId.SATELLITE]
},
disableDoubleClickZoom: true,
scrollwheel: false,
draggableCursor: "crosshair"
}
map = new google.maps.Map(document.getElementById("map-canvas"), myOptions);
poly = new google.maps.Polyline({ map: map });
for(var i = 0; i < scope.paths['j'].length; i++) {
var lat = scope.paths['j'][i]['k']
var lng = scope.paths['j'][i]['D']
var lat_lng = new google.maps.LatLng(lat, lng);
path.push(lat_lng);
}
poly.setPath(path);
google.maps.event.addListener(map, "click", function(evt) {
if (path.getLength() === 0) {
path.push(evt.latLng);
poly.setPath(path);
} else {
service.route({
origin: path.getAt(path.getLength() - 1),
destination: evt.latLng,
travelMode: google.maps.DirectionsTravelMode.DRIVING
}, function(result, status) {
if (status == google.maps.DirectionsStatus.OK) {
for (var i = 0, len = result.routes[0].overview_path.length;
i < len; i++) {
path.push(result.routes[0].overview_path[i]);
}
}
});
}
//console.log(path);
});
}
}
}]);
I want to call scope.updateMap from the button click but it is not firing in the console.
This won't work because the ng-click is outside the directive.
You should move the function updateMap to the $scope of myMapCTRL
Having dug around a little more, it seems quite normal to use a shared service to communicate between a controller and a directive.
The general idea is this:
HTML
<div ng-controller="myMapCTRL as myMapctrl">
<div id="panel">
<input ng-click="updateMap()" type=button value="Remove Path">
</div>
<my-map-with-path id="map-canvas" class="map-canvas" ng-if="dataHasLoaded" ></my-map-with-path>
</div>
SharedService
app.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.doSomething = function() {
$rootScope.$broadcast('messageBroadcast');
};
return sharedService;
});
Controller
app.controller('myMapCTRL', ['$scope', 'mySharedService',
function($scope, sharedService){
$scope.updateMap = function() {
sharedService.doSomething();
}
}]);
Directive
app.directive('myMapWithPath', [function() {
return{
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: 'myMapCTRL',
link: function(scope, element, attrs){
scope.$on('messageBroadcast', function() {
console.log('in directive broadcast message');
});
...
}
}
}]);
The idea seems to be that the controller calls a function in the shared service which "broadcasts" a message out. The directive waits for that message and when it is received, it does something amazing.
I am not sure if I need to inject the shared service into the directive or link function but it seems to work without it.

How to make AngularJS compile the code generated by directive?

Please help me on, How can we make AngularJS compile the code generated by directive ?
You can even find the same code here, http://jsbin.com/obuqip/4/edit
HTML
<div ng-controller="myController">
{{names[0]}} {{names[1]}}
<br/> <hello-world my-username="names[0]"></hello-world>
<br/> <hello-world my-username="names[1]"></hello-world>
<br/><button ng-click="clicked()">Click Me</button>
</div>
Javascript
var components= angular.module('components', []);
components.controller("myController",
function ($scope) {
var counter = 1;
$scope.names = ["Number0","lorem","Epsum"];
$scope.clicked = function() {
$scope.names[0] = "Number" + counter++;
};
}
);
// **Here is the directive code**
components.directive('helloWorld', function() {
var directiveObj = {
link:function(scope, element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if(strUserT) {
strTemplate = "<DIV> Hello" + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
element.replaceWith(strTemplate);
},
restrict: 'E'
};
return directiveObj;
});
Here's a version that doesn't use a compile function nor a link function:
myApp.directive('helloWorld', function () {
return {
restrict: 'E',
replace: true,
scope: {
myUsername: '#'
},
template: '<span><div ng-show="myUsername">Hello {{myUsername}}</div>'
+ '<div ng-hide="myUsername">Sorry, No user to greet!</div></span>',
};
});
Note that the template is wrapped in a <span> because a template needs to have one root element. (Without the <span>, it would have two <div> root elements.)
The HTML needs to be modified slightly, to interpolate:
<hello-world my-username="{{names[0]}}"></hello-world>
Fiddle.
Code: http://jsbin.com/obuqip/9/edit
components.directive('helloWorld', function() {
var directiveObj = {
compile:function(element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if(strUserT) {
strTemplate = "<DIV> Hello " + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
element.replaceWith(strTemplate);
},
restrict: 'E'
};
return directiveObj;
});
Explanation: The same code should be used in compile function rather than linking function. AngularJS does compile the generated content of compile function.
You need to create an angular element from the template and use the $compile service
jsBin
components.directive('helloWorld', ['$compile', function(compile) {
var directiveObj = {
link: function(scope, element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if (strUserT) {
strTemplate = "<DIV> Hello" + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
var e = angular.element(strTemplate);
compile(e.contents())(scope);
element.replaceWith(e);
},
template: function() {
console.log(args);
return "Hello";
},
restrict: 'E'
};
return directiveObj;
}]);

Resources