Transcluded input ng-model does not update scope variable passed to directive - angularjs

I have a directive that is essentially a complicated label tag that transcludes an input element and takes the input box's ng-model as a parameter.
<div switch model="testObject.switch">
<input type="checkbox" ng-model="$parent.testObject.switch">
</div>
Prior to upgrading to angular 1.4, clicking on the directive updated the ng-model and triggered the watch inside the directive.
Now, clicking on the directive still affects the input box, but the value inside the directive is unaffected.
I would appreciate any insight into what caused this change and how to fix it.
fiddle

I have updated your fiddle with working code. If you require ngModel in your directive and watch its $modelValue, you are able to get the behavior that you are looking for.
HTML:
<div switch ng-model="testObject.switch">
Directive:
booleanSwitchModule.directive('switch', [function () {
return {
scope: {},
require: "?^ngModel",
link: function (scope, elem, attr, ngModel) {
var timesChanged = 0;
scope.$watch(function() {return ngModel.$modelValue; }, function (val) {
if (val != undefined) {
alert("model changed " + ++timesChanged + " times");
scope.switchPosition = scope.model;
}
});
},
restrict: 'EA',
replace: true,
transclude: true,
template: '<label class="switch">' +
'directive scope model: {{ngModel}}' +
'<span ng-transclude></span>' +
'</label>',
}
}]);
Here is the updated fiddle: https://jsfiddle.net/62911kx5/3/

Related

Angular ngPaste not displaying up to date values within my Directive

Please see relevant jsFiddle
Within my directive whenever I paste an item on the 'searchBar' or text box I am not getting the updated value of the element text.
Here is my directive:
app.directive('searchBar', function() {
return {
restrict: 'AE',
replace: true,
template: '<input type="text" ng-model="searchData" placeholder="Enter a search" id="searchbarid" />',
link: function(scope, elem, attrs) {
elem.on('paste', function(evt) {
alert(evt.target.value);
});
}
};
});
Wrap the event inside of a $timeout of 0 so that it evaluates in the next $digest after angular evaluates all of its watches.
elem.on('paste', function(evt) {
$timeout(function() {
alert(evt.target.value);
},0)
});
http://jsfiddle.net/36qp9ekL/530/

How to create a angular input directive that works with form validation

How do I create an angular directive that adds a input in a form but also works with form validation.
For instance the following directive creates a text input:
var template = '\
<div class="control-group" ng-class="{ \'error\': {{formName}}[\'{{fieldName}}\'].$invalid}">\
<label for="{{formName}}-{{fieldName}} class="control-label">{{label}}</label>\
<div class="controls">\
<input id="{{formName}}-{{fieldName}}" name="{{fieldName}}" type="text" ng-model="model" />\
</div>\
</div>\
';
angular
.module('common')
.directive('formTextField', ['$compile', function ($compile) {
return {
replace: true,
restrict: 'E',
scope: {
model: '=iwModel',
formName: '#iwFormName',
fieldName: '#iwFieldName',
label: '#iwLabel'
},
transclude: false,
template: template
};
}])
;
However the input is not added to the form variable ($scope.formName). I've been able to work around that by using the dynamicName directive from the following stackoverflow question Angularjs: form validation and input directive but ng-class will still not work.
Update
It now seems to be working but it feels like a hack. I thought I needed the scope to grab the form and field names however I can read that directly from the attributes. Doing this shows the field in the controllers scope form variable. However the ng-class attribute does not apply. The only way around this is to add the html element a second time once the scope is available.
jsFiddle here
var template = '\
<div class="control-group">\
<label class="control-label"></label>\
<div class="controls">\
<input class="input-xlarge" type="text" />\
</div>\
</div>\
';
angular
.module('common')
.directive('formTextField', ['$compile', function ($compile) {
return {
replace: true,
restrict: 'E',
scope: false,
compile: function compile(tElement, tAttrs, transclude) {
var elem = $(template);
var formName = tAttrs.iwFormName;
var fieldName = tAttrs.iwFieldName;
var label = tAttrs.iwLabel;
var model = tAttrs.iwModel;
elem.attr('ng-class', '{ \'error\': ' + formName + '[\'' + fieldName + '\'].$invalid }');
elem.find('label').attr('for', formName + '-' + fieldName);
elem.find('label').html(label);
elem.find('input').attr('id', formName + '-' + fieldName);
elem.find('input').attr('name', fieldName);
elem.find('input').attr('ng-model', model);
// This one is required so that angular adds the input to the controllers form scope variable
tElement.replaceWith(elem);
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
// This one is required for ng-class to apply correctly
elem.replaceWith($compile(elem)(scope));
}
};
}
};
}])
;
when I do something like this, I use the directive compile function to build my html prior to it being processed. For example:
myApp.directive('specialInput', ['$compile', function($compile){
return {
// create child scope for control
scope: true,
compile: function(elem, attrs) {
// use this area to build your desired dom object tree using angular.element (eg:)
var input = angular.element('<input/>');
// Once you have built and setup your html, replace the provided directive element
elem.replaceWith(input);
// Also note, that if you need the regular link function,
// you can return one from here like so (although optional)
return function(scope, elem, attrs) {
// do link function stuff here
};
}
};
}]);

AngularJS model not updating while typing

In the following AngularJS code, when you type stuff into the input field, I was expecting the div below the input to update with what is typed in, but it doesn't. Any reason why?:
html
<div ng-app="myApp">
<input type="text" ng-model="city" placeholder="Enter a city" />
<div ng-sparkline ng-model="city" ></div>
</div>
javascript
var app = angular.module('myApp', []);
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
http://jsfiddle.net/AndroidDev/vT6tQ/12/
Add ngModel to the scope as mentioned below -
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
Updated Fiddle
It should be
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
since you are binding the model to city
JSFiddle
The basic issue with this code is you aren't sharing "ngModel" with the directive (which creates a new scope). That said, this could be easier to read by using the attributes and link function. Making these changes I ended up with:
HTML
<div ng-sparkline="city" ></div>
Javascript
app.directive('ngSparkline', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var newElement = '<div class="sparkline"><h4>Weather for {{' + attrs.ngSparkline + '}}</h4></div>';
element.append(angular.element($compile(newElement)(scope)));
}
}
});
Using this pattern you can include any dynamic html or angular code you want in your directive and it will be compiled with the $compile service. That means you don't need to use the scope property - variables are inherited "automatically"!
Hope that helps!
See the fiddle: http://jsfiddle.net/8RVYD/1/
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
the issue is that require option means that ngSparkline directive expects ngModel directive controller as its link function 4th parameter. your directive can be modified like this:
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{someModel}}</h4></div>',
link: function(scope, element, attrs, controller) {
controller.$render = function() {
scope.someModel = controller.$viewValue;
}
}
}
});
but this creates someModel variable in scope. that I think isn't necessary for this use case.
fiddle

Why does the ngModelCtrl.$valid not update?

I'm trying to create a directive that contains an inputfield with a ng-model and knows if the inputcontrol is valid. (I want to change a class on a label within the directive based on this state.) I want to use the ngModelController.$valid to check this, but it always returns true.
formcontroller.$valid or formcontroller.inputfieldname.$valid do work as exprected, but since im trying to build a reusable component using a formcontroller is not very handy because then i have to determine what field of the form corresponds with the current directive.
I dont understand why one works and one doesnt, because in de angular source it seems to be the same code that should manage these states: The ngModelController.$setValidity function.
I created a test directive that contains a numeric field with required and a min value. As you can see in the fiddle below, the model controller is only triggered during page load and after that never changes.
jsfiddle with example directive
Directive code:
angular.module('ui.directives', []).directive('textboxValid',
function() {
return {
restrict: 'E',
require: ['ngModel', '^form'],
scope: {
ngModel: '='
},
template: '<input type="number" required name="somefield" min="3" ng-model="ngModel" /> '+
'<br>Form controller $valid: {{formfieldvalid}} <br> ' +
'Model controller $valid: {{modelvalid}} <br>'+
'Form controller $valid: {{formvalid}} <br>',
link: function (scope, element, attrs, controllers) {
var ngModelCtrl = controllers[0];
var formCtrl = controllers[1];
function modelvalid(){
return ngModelCtrl.$valid;
}
function formvalid(){
return formCtrl.$valid;
}
scope.$watch(formvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
});
scope.$watch(modelvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one only gets triggered on pageload
alert('modelvalid ' + newVal );
});
}
};
}
);
Can someone help me understand this behaviour?
I think because you're watching a function and the $watch is only execute when this function is called !!
Watch the model instead like that :
scope.$watch('ngModel', function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one triggered each time the model changes
alert('modelvalid ' + ngModelCtrl.$valid );
});
I figured it out..
The textboxValid directive has a ng-model directive, and so does the input that gets created by the directive template. However, these are two different directives, both with their own seperate controller.
So, i changed my solution to use an attribute directive like below. This works as expected.
.directive('attributetest',
function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '='
},
link: function (scope, element, attrs, ngModelCtrl) {
function modelvalid(){
return ngModelCtrl.$valid;
}
scope.$watch(modelvalid, function(newVal,oldVal){
console.log('scope.modelvalid = ' + ngModelCtrl.$valid );
});
}
};
});

Angularjs directive for checkbox with ng-model

I have a directive that work on check-box that also has ng-model.
In the directive on link function the check box not get the value of the model.
It is work if add timeout( doesn't matter how long, even with 0 ).
My control and directive:
var myApp = angular.module("myApp",[])
.directive("checkBox", function($timeout){
return {
restrict: 'A',
link: function (scope, element, attrs, ctrl) {
console.log("Check box is : " + element[0].checked);
scope.message += "Check box is : " + element[0].checked + " , ";
$timeout(function(){
scope.message += "Check box is : " + element[0].checked;
console.log("Check box is : " + element[0].checked);
},0);
}
}
});
function myCtrl($scope){
$scope.checkBoxModel = true;
$scope.message = "";
}
HTML:
<div ng-app="myApp" ng-controller="myCtrl" >
<input type="checkbox" ng-model="checkBoxModel" check-box>
<br/>
{{message}}
</div>
Fiddel - http://jsfiddle.net/myyjL/
Thanks in advance.
You probably should not do what you are trying to do, but it is possible.
Here is a plunkr that works, without timeout:
http://jsfiddle.net/myyjL/2/
.directive("checkBox", ['$timeout', function($timeout){
return {
restrict: 'A',
replace: false,
scope: {
ngModel:'='
},
transclude: true,
link: function (scope, element, attrs, ctrl) {
scope.$watch('ngModel', function(newValue){
scope.$parent.message = 'Checkbox value is: ' + newValue;
});
}
}
}])
The problem with that approach is that your directive knows about stuff that is outside of it (as in the {{message}} variable). That is a bad design and you should rework that. Also, in your fiddle you use the element[0].checked, while you can easily use the ng-modal value. But the ng-modal might not be there. Also, the input might not be a real checkbox, so your approach also fails. How to fix that? I'm gonna write you a demonstrative plunkr in a sec:
http://plnkr.co/edit/jyY6sQwXmtlGOvpdzETR?p=preview
angular.module('myApp', [])
.directive('box', [function(){
return {
restrict: 'E',
scope: {
ngModel: '='
},
templateUrl: 'box.tpl.html',
replace: true
};
}])
.controller('MyController', ['$scope', function MyController($scope){
$scope.checkboxValue = true;
}]);
What we did here is make sure our directive is always a checkbox by providing the checkbox inside the template. The you are able to do styling etc, of your directive.

Resources