I'm trying to have some code run during the link callback of the ngModel directive using AngularJS on a select field that uses ngOptions.
module.directive("ngModel",function(){
console.log('ng-model called');
return {
restrict: 'A',
priority: -1, // give it lower priority than built-in ng-model
link: function(scope, element, attr) {
console.log('watching');
scope.$watch(attr.ngModel,function(value){
if (value){
console.log("changing");
}
});
}
}
});
See this fiddle which demonstrates the problem:
http://jsfiddle.net/d3r3zwLj/3/
The first select field is populated using ng-options, while the second has its options explicitly written out in html. If you open up the console, you can see that you only see the "changing" message when you change the second select field. Changing the first does nothing.
You'll also notice immediately that you only see 'ng-model called' and 'watching' once, even though there are two fields with ng-model on them.
I'd expect ngModel directive to work on both select fields. What is ng-options doing that is preventing the ngModel from working?
Thanks!
Looking at the doc for ng-options:
https://docs.angularjs.org/api/ng/directive/ngOptions
"Directive Info This directive executes at priority level 0."
If you change the priority of the directive to 0, it runs successfully. I'm not sure what all the implications are there, but that's why it was happening.
When you use the ng-options directive, it has another optional ng-model directive, which actually sets the default value to be shown from the options. So in your case, the second list does not have a ng-options directive which makes ng-model look like a regular one. Maybe you can just change the name of your directive.
This is a bad way to update functionality of built in directive, decorators are there for this purpose. Check the documentation
Related
I'm developing the AngularJS client. I've got a custom directive used as an attribute. The directive checks the access level of the element and sets it to be disabled if the current user is not allowed to use it.
The problem begins when the same element has ng-disabled attribute. In this case the ng-disabled sets the ability of the element, never mind of what I set in my custom directive.
For example I have a button that should be disabled in case the form is invalid. At the same time I'd like to use my custom directive in order to set the button to be disabled if the user doesn't have a permition to use it.
<button ng-disabled="myFrm.myCtrl.$invalid" my-custom-directive="controlName"/>
Inside myCustomDirective I check if the named control is allowed to be activated by the user. If not - I set the disabled attribute to the element. But in case myFrm.myCtrl.$invalid is false ng-disabled removes the disabled attribute and the button is enabled.
Is there any solution to this problem? How can I prevent from ng-disabled to perform its operation?
Does your example directive reflect how you applied your actual directive to the button element? I noticed that your example directive is not following the correct naming convention of making each upper case letter in the directive name a lower case letter with a leading hyphen. i.e. myCustomDirective should be applied to the button like so:
<button ng-disabled="myFrm.myCtrl.$invalid" my-custom-directive="controlName"/>
Otherwise your directive will not be complied or linked for that button.
The only other thing I could think of without seeing the actual code is the priority of the directive. According to the angular docs for the ngDisabled directive:
This directive executes at priority level 100.
By default custom directives have a priority of 0. As long as you have not changed this directive parameter your directive should be compiled after ng-disabled and linked after ng-disabled and therefore have the final say on whether or not the button is disabled or enabled.
-- EDIT --
You should add a watch to myFrm.myCtrl.$invalid as was suggested by another and reapply your directives rules whenever the value changes. You don't need to pass the whole controller into your directive though, you could simply use two way binding on myFrm.myCtrl.$invalid. I am not sure how $watch priority works yet. I am hoping that because your directive will be compiled first it will apply its watch on myFrm.myCtrl.$invalid after ngDisabled does and hopefully that means it will handle value changes of myFrm.myCtrl.$invalid after ngDisable does so that your directive has the final say on what rule is applied.
Finaly I've found a solution to my problem. Instead of watching ng-disabled variable I've totaly diconnected the ng-disabled from its variable. In order to do it I've set the ng-disabled to be allways true in my own directive. My directive is an attribute, so after I've tried some different ways to do it, the solution was to add in my directive constructor the following code:
this.isDisabled = "true";
this.attrs["ngDisabl​ed"] = this.isDisabled;
It's very very important to set the this.isDisabled to "true" as a string and not boolean. Otherwise it doesn't work.
Markup
<div ng-app>
<div ng-controller="myCtrl">
<select ng-options="i for i in [1,2,3]"></select>
</div>
</div>
With this markup, options are not populated in select. When i provide ng-model along with ng-options it works as expected.
Why ng-model is required for the ng-options to populate the options?
ngOptions needs model in order to determine which option should be selected by default or which model to write selected object/value when selection changes.
If there was no ngModel directive, it would be difficult to access selected value, and it potentially lead to bad-practice code when people either query DOM to determine the value or write their own directives for this purpose. In both cases this is not ideal as this type of things is the key idea of Angular form elements directives.
Also don't forget that ngOptions in conjunction with ngModel allow developer to bind entire objects as the "value" of select elements, which would be tricky without ngModel.
Finally, if for some reason one doesn't want to have any model on select (although it makes little sense), it's possible to render options with ngRepeat.
If there is no model bound to the select, after choosing an option where would the selection be written out to?
As for the reason why the options are not displayed you can see the source code:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngOptions.js#L355
return {
restrict: 'A',
terminal: true,
require: ['select', '?ngModel'],
link: function(scope, selectElement, attr, ctrls) {
// if ngModel is not defined, we don't need to do anything
var ngModelCtrl = ctrls[1];
if (!ngModelCtrl) return;
The link function short cuts out and all the render functions are not created.
From the docs:
ngOptions should be used when the model needs to be bound to a non-string value
The very point of ngOptions is to provide values for the model, not to merely render option elements. Therefore it doesn't make sense to be used without ngModel.
I've been trying to figure out a way to trigger the validation routines on an input element when a button is clicked in the same form. I've been looking at a few different ways of doing this. What seems to be the least convoluted is to create a directive that modifies an input button to fire the $validate method on the target form element. I've set this up without too much trouble but I've gotten blocked at how to modify the ngClick event handler so that it triggers the $validate while leaving the original HTML-defined ngClick intact.
I was attempting to use the directive template function to extract the original ngClick method and chain it to the new ngClick function defined in the directive. This started to turn into a mess quite quickly and I'm concerned about how brittle it might be.
Is there a way to intercept the ngClick handler in a directive and to still have the original functionality intact?
Alternately, I'm open to suggestions about how to fire the validation routines on the input field when the button is clicked with minimal involvement of the controller layer.
This is a classical example of an XY-question (if not a double-XY-question).
You don't need to "chain event handlers" (whatever you mean by that). Neither do you need to, I think, trigger the validation manually just because you are validating against external data.
Validation in Angular just runs - and it is not meant to be triggered other than by changing the data.
To add your own custom validator you need to create a directive (which it seems like you did). In that directive you probably need to specify what you are validating against, like an array of strings against which you want to check for duplicates.
Let's say, for simplicity, that you want to validate against another value in the ViewModel. Suppose, this how it would be used:
<input ng-model="bar">
<form name="form1">
<input ng-model="foo" not-equal-to="bar">
</form>
<span ng-show="form1.$error.notEqualTo">error: foo is equal to bar</span>
So, you need to create a directive notEqualTo that adds a validator to the ngModel.$validators pipeline. This directive also needs to $watch for changes to bar and re-set the validity:
app.directive("notEqualTo", function(){
return {
require: "ngModel",
scope: {
notEqualTo: "="
},
link: function(scope, element, attrs, ngModel){
// register "notEqualTo" validator
ngModel.$validators.notEqualTo = function(modelValue){
return validate(modelValue, scope.notEqualTo);
};
// rerun validation on changes to scope.notEqualTo
scope.$watch("notEqualTo", function(){
ngModel.$setValidity("notEqualTo",
validate(ngModel.$modelValue, scope.notEqualTo));
});
function validate(one, other){
return one !== other;
}
}
};
});
plunker
I have an input field with a number in it. I want to make it possible to edit the field and update it. So it is like an input field with the number 5 and when i change it to 6 it has to automatically update it self. So if i refresh the page the number should have been updated to the number 6. I've tried it with ng-change but it didnt work.. The code that i used was:
<input type="text" ng-change='change(di)' value="{{di.hours}}">
To test the ng-change i used the following code in my Controller
$scope.change = function(data){
alert(data);
};
The problem was, it didn't alert anything when i had changed my input.
Try using ng-model to bind input to some property of scope.
To detect changes of scope you can use $watch function.
Try using ng-model="{{di.hours}}" instead to bind input, you will get value automatically.
And do not use ng-change
you can do $watch in angular controller like
scope.$watch('di.hours', function(newVal, oldVal) {
alert(newVal);
});
I am decorating a standard text input with the jquery plugin for tags input, which essentially replaces the text input in my HTML with a text area where users can write tag names and hit enter to have them converted to discrete graphical tags. (see demo at http://xoxco.com/projects/code/tagsinput/)
I am placing an ngModel directive in the original text input.
I am able to keep my scope updated with changes made in the tags input field by listening to the change handler from the plugin, parsing the ngModel attribute from the original text input html tag, and updating the scope directly.
However, the issue is that when Angular detects a change in the model and updates the view using binding, it is setting the value on the original text input which does not cause any sort of event I can bind to to know when to update the plugin's value, since "change" only fires due to user input.
Is there a way to modify the default ngModel directive behavior to have it fire an event or run a function I specify when it is processing bindings, specifically from model to view?
You need to override the ngModel.$render function
directive.tabbable = function() {
return {
require: 'ng-model',
link: function(scope, element, attrs, ngModel) {
// do work to register the jQuery widget
ngModel.$render = function() {
// read ngModel.$viewValue and update the jQuery widget with it.
}
}
};
};