Scope variables are not binded in directive for jQuery plugin - angularjs

I'm trying to bind 2 values from an input field to a scope variable. First is an input's value (color, written as text), and the second one is an attribute value (opacity value). I want them to change, and, as well, I want so their value be outputted.
myApp.directive( 'watchOpacity', function() {
return {
restrict: 'A',
link: function( scope, element, attributes ) {
scope.$watch( attributes.opacity, function(value) {
console.log( 'opacity changed to', value );
});
}
};
})
Plunker demo
The problem is that neither the input's value, nor the attribute's value is displayed/binded.

Use the change callback jQuery MiniColors provides. It will have hex and opacity passed in as arguments, which you can use to set your scope.data properties.
You need to wrap the setting of those properties in a scope.$apply callback to ensure a digest cycle is run afterwards, so that your view is updated:
.directive( 'watchOpacity', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
watchOpacity: '='
},
link: function( scope, element, attributes, ngModel ) {
$timeout(function(){
element.attr('data-opacity', scope.watchOpacity);
$(element).minicolors({
opacity: true,
defaultValue: ngModel.$modelValue || '',
change: function(hex, opacity) {
ngModel.$setViewValue(hex);
scope.$apply(function() {
scope.watchOpacity = opacity;
})
}
});
});
}
}
})
Using this directive, your view would look like this (ng-init is optional depending upon whether or not you require default values or if you've placed them in the controller):
<input type="text" watch-opacity="data2.opacity" ng-model="data2.color"
ng-init="data2.color = '#0000FF'; data2.opacity = 0.5;" />
Working fork of your demo

Firstly : You've defined data-opacity in your element , while you want to use it as attributes.opacity , which is not even logically correct
Secondly : in your directive , if you want to read the value of your current directive , I mean this :
watch-opacity="data.opacity"
You need to say something like this in your directive :
link:function(scope,element,attributes){
var data_opacity = attributes.watchOpacity// you have written attribute.opacity!
}
I dont know what you are going to do :(

Related

Value in ngModel not updated in Angular input view

I need to format the input values so I create a directive that use a template with require: 'ngModel' because I have to use ngModelController functions ($parsers, $formatters, etc.).
This is my HTML:
<div ng-model="myInputValue" amount-input-currency=""></div>
{{myInputValue}}
This is my directive:
.directive('amountInputCurrency', [function(){
return {
templateUrl: '../amountInputCurrency.tmpl.html',
require: 'ngModel',
restrict: 'A',
link: function(scope, elem, attrs, model) {
// ...
}
}
}
And this is my template:
<input type="text" ng-model="myInputValue">
The problem is that I can't updated the view after formatting the inserted value. For example if I write '1' I want change the value in this way:
model.$formatters.push(function(value) {
return value + '00';
}
Alternative I try to set an event in this other way:
<input type="text" ng-model="myInputValue" ng-blur="onBlur()">
scope.onBlur = function() {
model.$viewValue = model.$viewValue + '00';
// or model.$setViewValue(model.$viewValue + '00';);
model.$render();
};
The model.$viewValue changes, myInputValue (in the HTML with {{myInputValue}}) changes but not the value showed in the input box... which is the problem? Thanks!
----------------UPDATE----------------
Probably the problem is because I have 2 ng-model (one in the HTML and one in the template): https://github.com/angular/angular.js/issues/9296
How can I do? Both model refer to the same model...
Formatters change how model values will appear in the view.
Parsers change how view values will be saved in the model.
//format text going to user (model to view)
ngModel.$formatters.push(function(value) {
return value.toUpperCase();
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value.toLowerCase();
});
Try using $parsers to change the view to your desired value.
I hope this will help you.
Update:
angular.module('components', []).directive('formatParse', function() {
return {
restrict: 'E',
require: 'ngModel',
scope: { model: "=ngModel" },
template: '<input type="text" data-ng-model="model"></input><button type="button" data-ng-click="clickedView()">SetView</button><button type"button" data-ng-click="clickedModel()">SetModel</button>',
link: function ($scope, el, attrs, ngModelCtrl) {
format = "MMM Do, YYYY H:mm";
console.log($scope.model);
console.log(ngModelCtrl);
$scope.clickedView = function () {
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
};
$scope.clickedModel = function () {
$scope.model = 12; // Put here whatever you want
};
ngModelCtrl.$parsers.push(function (date) {
console.log("Parsing", date)
return date; // Put here the value you want to be in $scope.model
});
ngModelCtrl.$formatters.push(function (date) {
console.log("Formatting", date);
console.log($scope.model);
console.log(ngModelCtrl);
return +date * 2; // Put here what you want to be displayed when clicking setView (This will be in ngModelCtrl.$viewValue)
});
}
}
});
angular.module('someapp', ['components']);
Try using this code and tell if this helped to get the result you wanted.
If it does I suggest, to console.log the ngModelCtrl that you way you will understand more about the inner flow of angular.
In addition, just so you have some more information,
When you edit the input in the view the formatters function are fired to change the model accordingly.
If the value that has been entered is not valid you can return in your formatters function the ngModelCtrl.$viewValue to keep $scope.model with his old and true information.
When you change your scope variable (in your case $scope.model) the parsers functions will be fired to change the view value. (You don't need to use $render, you just need to decide when you want to change your $scope.model),
I suggest instead of using $setViewValue put the value you want in your scope variable and the parsers will act accordingly.

Re-evaluate directive attribute expression

I'm having troubles in re-evaluating an expression passed as attribute of a custom angular (1.2.28) directive.
I've tried all the possible combination of $eval, $parse as well as isolated and non-isolated scope. I can't wrap my mind around this.
I have something like this:
<div ng-repeat="item in dataset">
<my-directive
show-tooltip="user.level=='visitor' && item.memberOnly"
content-tooltip="isAdded(item) && 'Remove Me' || 'Add Me'">
<my-directive>
</div>
The problem is that user.level can change because for example the user logged in and the (enclosing) scope function isAdded can returns different values depending if the items was already added to a list or not.
The directive:
angular.module("MyModule", [])
.directive("myDirective", () {
return {
restrict: 'E',
priority: 999,
link: function(scope, elm, attrs) {
showTooltip = scope.$eval(attrs.showTooltip);
contentTooltip = scope.$eval(attrs.contentTooltip);
// This works
scope.$watch(attrs.contentTooltip, function(value) {
if( value and value != contentTooltip)
contentTooltip = value
});
// This never works
scope.$watch(attrs.showTooltip, function(value) {
if( value and value != showTooltip)
showTooltip = value
});
// Do things..
}
}
});
I don't know why but the first watch will work, the second will never work. I've used a similar approach with $parse but couldn't get it to work either.
Maybe I'm doing this totally wrong
Look into using attrs.$observe instead:
attrs.$observe('showTooltip', function(){
})

In angularjs ui-bootstrap, how can I set initial option in bootstrap-formhelpers-states, and avoid '? undefined:undefined ?' option?

I'm trying to resolve an old angularjs 'issue' which is actually expected behavior: 'Adding ng-model to predefined Select element adds undefined option' (https://github.com/angular/angular.js/issues/1019).
I'm working with ng 1.4.9, ui-bootstrap, ui-router and am trying to use 'bootstrap-formhelpers-states' for a State select list. I do have jQuery in the project in order to support FullCalendar, which I have an ng directive for, but I'm trying not to use it for anything else.
I can get the States list to work and without having to include any bootstrap js, but I'm not getting the binding to Model to work correctly on init, which is something many people have trouble with.
From this question (Why angularJS´s ui-view breaks Bootstrap Formhelper) I got a directive for Countries and adapted it for States:
angular.module("myApp").directive('statepicker', function ($parse) {
return {
restrict: 'A', // It is an attribute
require: '?ngModel', // It uses ng-model binding
scope: {
ngModel: '='
},
link: function (scope, elem, attrs, ctrl) {
// Add the required classes
elem.addClass('bfh-states');
// First we initialize the selec with the desired options
elem.select({
filter: (elem.attr('data-filter') == 'true') ? true : false
}).on('change', function() {
// Listen for the change event and update the bound model
return scope.$apply(function () {
return scope.ngModel = elem.val();
});
});
scope.$watch('ngModel', function (newVal, oldVal) {
if (newVal != oldVal) {
elem.val(newVal);
});
// Initialize the states with the desired options
return elem.bfhstates({
flags: (elem.attr('data-flags') == 'true') ? true : false,
country: 'US',
state: scope.ngModel || 'CA',
blank: false
});
}
};
});
So if i don't add 'ng-model' the the 'select' element, it initializes OK with the value from the directive options (CA), but with the model binding, it doesn't; something to do with it not being a valid option in the list (but it is). The problem is when I'm editing an exiting address, I want the list to be set from a variable model value, but when set via the initial directive options, isn't changeable.
HTML:
<select statepicker id="stateList" name="state" class="form-control" ng-model="state" ng-init="initState()"></select>
<button ng-click="setState('NY')">Set State</button>
With the model binding, I can use a button and a watcher to set the value after things load, but trying to do so via 'ng-init' just doesn't work.
Controller functions:
$scope.state = 'IL';
$scope.initState = function() {
$scope.setState('AZ');
};
$scope.setState = function(val) {
$scope.state = val;
};

angularjs directive - get element bound text content

How do you get the value of the binding based on an angular js directive restrict: 'A'?
<span directiverestrict> {{binding}} </span>
I tried using elem[0].innerText but it returns the exact binding '{{binding}}' not the value of the binding
.directive('directiverestrict',function() {
return {
restrict:'A',
link: function(scope, elem, attr) {
// I want to get the value of the binding enclosed in the elements directive without ngModels
console.log(elem[0].textContent) //----> returns '{{binding}}'
}
};
});
You can use the $interpolate service, eg
.directive('logContent', function($log, $interpolate) {
return {
restrict: 'A',
link: function postLink(scope, element) {
$log.debug($interpolate(element.text())(scope));
}
};
});
Plunker
<span directiverestrict bind-value="binding"> {{binding}} </span>
SCRIPT
directive("directiverestrict", function () {
return {
restrict : "A",
scope : {
value : '=bindValue'
},
link : function (scope,ele,attr) {
alert(scope.value);
}
}
});
During the link phase the inner bindings are not evaluated, the easiest hack here would be to use $timeout service to delay evaluation of inner content to next digest cycle, such as
$timeout(function() {
console.log(elem[0].textContent);
},0);
Try ng-transclude. Be sure to set transclude: true on the directive as well. I was under the impression this was only needed to render the text on the page. I was wrong. This was needed for me to be able to get the value into my link function as well.

How to watch for a property change in a directive

I have an angular directive (with isolate scope) set up like this
<div ng="$last" somedirective datajson=mydata myprop="{{ mydata.myspecialprop }}"></div>
(which actually gets rendered multiple times because it's inside an ng-repeat.)
Following the instructions of this SO answer, I tried to observe myprop for changes in the directive, however, the code inside the $scope.watch never runs even when the property changes (on a click event). I also tried scope.$watch(attrs.myprop, function(a,b){...)and it never ran either. How do I watch for the change in myprop
myapp.directive('somedirective', function () {
return {
restrict: 'AE',
scope: {
datajson: '=',
myprop: '#'
},
link: function (scope, elem, attrs) {
scope.$watch(scope.myprop, function (a, b) {
if (a != b) {
console.log("doesn't get called when a not equal b");
} else {
}
});
}
};
}
Update: the click event that changes the property is handle in the controller and I'm guessing this isn't reflected back in the isolate scope directive so that $watch is never getting triggered. Is there a way to handle that?
When you use an interpolation binding (#) you cannot use scope.$watch, which is reserved for two-way bindings (=) or internal scope properties.
Instead, you need to use attrs.$observe which does a similar job:
link: function(scope, elem, attrs){
attrs.$observe('myprop', function(newVal, oldVal) {
// For debug purposes
console.log('new', newVal, 'old', oldVal);
if (newVal != oldVal){
console.log("doesn't get called when newVal not equal oldVal");
} else {
// ...
}
});
}
Also, everytime myprop change, the newVal will be different from its oldVal, so it is a bit weird that you skip that case which is the only one which will happen.
NOTE: you also forgot doublequotes for the datajson two-way binding: datajson="mydata"
Intead of passing a string, try it as variable
scope: {
datajson: '=',
myprop: '=' //change to equal so you can watch it (it has more sense i think)
}
then change html, removing braces from mydata.myspecialprop
<div ng="$last" somedirective datajson="mydata" myprop="mydata.myspecialprop"></div>

Resources