Ng-change is acting weird in a directive. It seems to have a delay in the digest cycle resulting in the wrong (previous) ngModel value in controller immediately after the change.
ngModel : '='
https://codepen.io/anon/pen/moEgdG
What's going on and how to fix?
With the ng-model directive on a component, I recommend using one-way (<) for the input and $setViewValue on the output:
app.directive('newTag', function(){
return {
template: `
<input ng-model="test" ng-change="change(test)"> <br/>
{{test}}
`,
restrict: 'E',
require: "ngModel",
scope: {
ngModel : '<',
},
link: function (scope, elem, attrs, ngModel) {
scope.change = function(val) {
ngModel.$setViewValue(val);
};
},
};
})
Usage:
<new-tag ng-model="tagValue" ng-change("newTagUpdate(tagValue)")>
</new-tag>
For more information, see
AngularJS ngModelController API Reference
AngularJS Developer Guide - Implementing custom form controls (using ngModel)
AngularJS Comprehensive Directive API Reference - require
Related
I want to know if a checkbox is checked using jqLite in angularjs. I'm using a directive to check and verify if the value is true or false. I tried this code angular.element('#myCheckbox').val(), but the value returned was false.
<input type="checkbox" id='myCheckbox' my-directive>
angular.js
.directive('myDirective', function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
console.log(angular.element('#myCheckbox').val());
},
};
})
I have an element directive and an attribute directive:
<my-element my-attribute="value"></my-element>
my-attribute is a directive which require ngModel:
app.directive('myAttribute', [
function() {
var definition = {
restrict: 'A',
require: 'ngModel',
link: function ($scope, $element, $attrs, ctrl) {...}
}
return definition;
my-element can be used without ng-model but the template of my-element contains input which always has ng-model. The my-attribute should be removed from my-element and added to input inside my-element. In my compile function in my-element directive I have:
var value = element.attr('my-attribute');
element.remove('my-attribute');
element.find('input').attr('my-attribute', value);
It works ok when attribute directive doesn't have require: 'ngModel'. But if I have ngModel required I get:
Error: [$compile:ctreq] Controller 'ngModel', required by directive 'myAttribute', can't be found!
Why? How can I fix this?
When you have require: 'ngModel' you'll receive an error if you don't have an ng-model attribute on that element the directive is on - that's what this directive property means. It says "give me the controller of ngModel's controller to be available as the 4th parameter in the link function.
To avoid this, if you don't always need to use it, you can add a ? sign before the ngModel and it means it's optional:
....
require: '?ngModel',
....
If you want to remove an attribute you should use
element.removeAttr('my-attribute');
I'm not sure about your intention in the link function, but you could make the link function in myAttribute run only if there is a ng-model attribute on the element:
require: 'ngModel',
compile: function(el, attr) {
if (attr.ngModel) {
return function(scope, elm, attr, ngModel) {
// link function
}
}
}
This is possible because angular doesn't try to find the required directives until it's going to execute the link function for that directive.
I'm using an AngularJS directive to generate two radio buttons. I'm hardcoding the "required" attribute into the directive template, but it doesn't behave as expected.
When no radio buttons are checked, it correctly displays this error message, as expected.
<p ng-show="form.$invalid">Error: the form is invalid.</p>
But it doesn't display this error message.
<p ng-show="form.gender.$invalid">Error: the gender input is invalid.</p>
Any idea why?
Please see the Plunker for details:
http://plnkr.co/edit/i5kVeX8WdrUbM83lT6O6?p=preview
You need to tell your directive to validate input fields initially. You can do this by defining $formatters callback checking validity of the fields:
app.directive('dRadio', function() {
return {
require: '^form',
restrict: 'E',
scope: { model: '=ngModel' },
template: '<input required type="radio" id="{{value}}" name="{{name}}" value="{{value}}" ng-model="model"><label for="{{value}}">{{label}}</label>',
link: function(scope, element, attrs, ngModelController) {
scope.name = attrs.name;
scope.value = attrs.value;
scope.label = attrs.label;
ngModelController[attrs.name].$formatters.unshift(function(value) {
ngModelController[attrs.name].$setValidity('required', !!scope.model);
return value;
});
}
};
});
Note, that you also need to add require: '^form' rule.
Demo: http://plnkr.co/edit/rac1RNNOWHs1wTnktJHE?p=preview
You can accomplish by doing this way
Plnkr
I changed the following line
<p ng-show="!test">Error: the gender input is invalid.</p>
Having a following template in templateUrl:
<input name="foo" ng-model="test">
directive:
app
.directive('bar', function() {
return {
link: function link(scope, element, attrs, ctrl) {
scope.$watch(scope.test, function(newVal) {
console.log(val);
});
},
restrict: 'E',
templateUrl: 'templates/foo.html'
};
});
can I two-way bind it in directive so I scope.$watch input variable?
I tried using ng-bind and ng-model, but I cannot access that variable in scope of my directive.
Edit
Added directive code.
Change:
scope.$watch(scope.test, ...
to
scope.$watch('test', ...
and it should work. The first argument to $watch is the (so called) watchExpression. It will be evaluated against the relevant scope. When using a string you can basically use everything you would also use in the views/templates.
Mind that this will break again if you start using isolated scopes.
I am trying to create a directive:
return {
restrict: 'A', // Attribute Directive
ngModel: '^ngModel',
scope: {
'ngModel': '='
},
link: function ($scope: ng.IScope, element, attrs, ctrl) {
var datePickerOptions = {
autoclose: true,
format: attrs.aceDatepickerFormat,
weekStart: attrs.aceDatepickerWeekstart
};
// Attach the datepicker events (must have Bootstrap.DatePicker referenced).
element.datepicker(datePickerOptions).next().on('click', function () {
$(this).prev().focus();
});
element.click(() => {
ctrl.$setViewValue(new Date());
});
}
};
In this example, when the click event occurs on the element, I wish to use ctrl.$setViewValue to the current date (this is a test).
When the link function is called, scope, element and atts are all populated correctly, however the ctrl is null.
The element is with a div with ng-controller set.
<div ng-controller="Controllers.FormElementsController">
<input class="form-control date-picker" id="id-date-picker-1" type="text"
ng-model="DatePickerValue"
ace-datepicker-weekstart="1"
ace-datepicker-format="dd-mm-yyyy"
ace-datepicker="" />
</div>
Why is no controller being passed here?
You have to use require to pull in the controller (ngModelController in your case):
return {
restrict: 'A', // Attribute Directive
require: '^ngModel',
You had it set to ngModel as the property name.
From the docs:
The myPane directive has a require option with value ^myTabs. When a
directive uses this option, $compile will throw an error unless the
specified controller is found. The ^ prefix means that this directive
searches for the controller on its parents (without the ^ prefix, the
directive would look for the controller on just its own element).