Angular.js - ng-change not firing when ng-pattern is $invalid - angularjs

I am using ng-pattern to validate some form fields, and I am using ng-change with it to watch and process any changes, however ng-change (or $scope.$watch) will only fire when the form element is in the $valid state! I'm new to angular, so I don't know how to solve this issue, although I suspect a new directive is the way to go.
How can I get ng-change to fire in both $invalid and $valid form element states, with ng-pattern still setting the form element states as before?
Html:
<div ng-app="test">
<div ng-controller="controller">
<form name="form">
<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" ng-change="change()" ng-model="inputtext"> Changes: {{ changes }}
</form>
<br>
Type in any amount of numbers, and changes should increment.
<br><br>
Now enter anything that isn't a number, and changes will stop incrementing. When the form is in the $invalid state, ng-change doesn't fire.
<br><br>
Now remove all characters that aren't numbers. It will increment like normal again. When the form is in the $valid state, ng-change will fire.
<br><br>
I would like ng-change to fire even when the the form is $invalid.
<br><br>
form.$valid: <font color="red">{{ form.$valid }}</font>
</div>
</div>
Javascript:
angular.module('test', []).controller('controller', function ($scope) {
$scope.changes = 0;
$scope.change = function () {
$scope.changes += 1;
};
});
I have created a working JS Fiddle which shows the problem I am having.
http://jsfiddle.net/JAN3x/1/
By the way, this angular issue also seems to be relevant:
https://github.com/angular/angular.js/issues/1296

You can change the behavior of your input by using ng-model-options.
Just add this attribute to your input and the ng-change event will fire:
ng-model-options="{allowInvalid: true}"
see: https://docs.angularjs.org/api/ng/directive/ngModelOptions

you just need to add
ng-model-options="{ updateOn: 'default' , allowInvalid:'true'}"
this indicates that the model can be set with values that did not validate correctly instead of the default behaviour.

Edit This was answered when ng-model-options was not available. Please see the top-voted answer.
you can write a simple directive to listen input event.
HTML:
<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" watch-change="change()" ng-model="inputtext"> Changes: {{ changes }}
JS:
app.directive('watchChange', function() {
return {
scope: {
onchange: '&watchChange'
},
link: function(scope, element, attrs) {
element.on('input', function() {
scope.$apply(function () {
scope.onchange();
});
});
}
};
});
http://jsfiddle.net/H2EAB/

Inspired by the Li Yin Kong ingenious solution :
His solution has an issue concerning the ndModel update (see the comments of his post).
My fix essentially changes the scope type of the directive. It lets directive access to controller scope (and methods)
Then, watch-change directive does not need an "instruction to eval" (change()) anymore, but only the "name of the controller method to call" (change).
And to get the new value of the input in this function, I pass the context (this = the input itself). So I can get the value or any property of it.
This way, we don't care about ngModel updates (or if the form is invalid, which was another issue of the initial solution : ngModel is deleted if form is invalid)
HTML :
<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" watch-change="change" ng-model="inputtext">
JAVASCRIPT :
app.directive('watchChange', function() {
return {
restrict : 'A',
link: function(scope, element, attrs) {
element.on('input', function(){
scope[attrs.watchChange](this);
})
}
};
});
DEMO : http://jsfiddle.net/msieurtoph/0Ld5p2t4/

Related

Angularjs: How to get value of input without ng-model

I need to make some inputs by ng-repeat, and in my json file I have in object where is a property called name, like this:
"url":"find_company",
"values":[
{
"name":"company name",
"type":"input_search"
},{
"name":"company_phone",
"type":"input_search"
}
]
I want to make search in DB, in search you can find by any field or by two or more field. Field called the same as property of object. So by ng-keyup I need to send to my function
search(field, value)
two arguments. I want to do something like this
<div ng-repeat="value in param.values">
<input ng-if="value.type == 'input_search'"
ng-keyup="search(value.name, this.text)"
type="text">
How can a send to function text of this input without using ng-model? Where this.text is value of input.
since you are using ng-keyup, you can retrieve input value with $event.target.value.
comment: this is fit for normal event like onclick, but not fit for angular.
refer the below example.
angular.module("app", [])
.controller("myCtrl", function($scope) {
$scope.showValue = function(val) {
alert(val);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="app" ng-controller="myCtrl">
<input type="test" ng-keyup="showValue($event.target.value)">
</div>
This is how you do it with ngModel:
<div ng-repeat="value in param.values">
<input ng-if="value.type == 'input_search'" ng-model="value.val" ng-keyup="search(value)" type="text">
And in your controller:
$scope.search = function( item ) {
console.log( item.val ); // Here you have the value using ngModel
console.log( item.name ); // Here you have the "name" property of the element inside the loop
}
As you can see, you CAN use ngModel and by passing the object itself to the function you can access its properties from the function in the controller.
Note that there's that this.text in the view - I don't know what it is exactly so I dropped it from the example to make things clearer, but you can use it in your code of course.
I know the question said without using ng-model. But I suspect you may want this because you want to customize when data-binding occurs. If that's the case, you can use ng-model-options with ng-change:
<input type="text" ng-model="yourModel" ng-model-options="{ updateOn: 'keyup' }" ng-change="search()" />
ng-change fires when the model has been updated, which is after keyup in this case. So the value of yourModel will be up to date when search() executes.

Delayed bind on ngModel

I am trying to create a typeahead directive that does not bind the typed text to the model while typing.
This is as such no problem, but I would like to use the ngModel directive for my binding so I am able to use something similar to
<input type="text" ng-model="model.field" typeahead="sourceForTypeahead" />
instead of my current approach which works as a charm
<input type="text" ng-model="tmpFieldForInput" typeahead="sourceForTypeahead" typeahead-model="model.field" />
I can't figure if it is possible to change the "target" of ng-model internally in the directive so I get the typed input, and then is able to set the external model when an result from the source is selected.
Use ngModelOptions to specify when you'd like to bind the input text to the model:
<input type="text" ng-model="myModel" ng-model-options="{ updateOn: 'blur' }">
<p>Hello {{myModel}}!</p>
There are different events you can trigger on, but in this case, the text will only be bound to the model once the end-user leaves focus from the field.
Additional resources: https://docs.angularjs.org/api/ng/directive/ngModelOptions
Like #lux has mentioned, the right way to go about it is to use ng-model-options
But in your case, the ideal solution would be to do wrap your input in a form and bind on submit:
<form name="myForm">
<input type="text" ng-model="myModel" ng-model-options="{ updateOn: 'submit' }">
<p>Hello {{myModel}}!</p>
<input type="submit" value="Submit">
</form>
This will bind the value to the model only when you click your Submit button. This of course can be put anywhere you please.
I found a solution after looking into an old version of the checkbox-list module.
The solution is to change the ngModel attribute on compile time and make it point to an internal property in the directive and then compile in the postlink method.
I have updated the plunker for others to see the prototype: http://plnkr.co/edit/LbHH2pJGX1Iii8ZqqSie?p=preview
(Stack requires me to post code - so here is the )
app.directive('typeahead', ['$parse', '$compile', function ($parse, $compile) {
return {
priority: 1000,
terminal: true,
scope: {
source: '=typeahead',
ngModel: '='
},
compile: function(tElement, tAttrs) {
tElement.removeAttr("typeahead");
tElement.attr("ng-model", "searchTerm");
return function(scope, element, attrs) {
$compile(element)(scope);
// all my logic here

ng-change not working on a text input

I am new to angular js. In my code there is color picker initialized from a text field. User changes the value of color and I want that color to be reflected as a background of a text in a span. It is not working. What is missing?
HTML:
<body ng-app="">
<input type="button" value="set color" ng-click="myStyle={color:'red'}">
<input type="button" value="clear" ng-click="myStyle={}">
<input type="text" name="abc" class="color" ng-change="myStyle={color:'green'}">
<br/>
<span ng-style="myStyle">Sample Text</span>
<pre>myStyle={{myStyle}}</pre>
</body>
Plunker - http://plnkr.co/edit/APrl9Y98Em0d6rxuzRDE?p=preview
However when I change it to ng-click it works.
ng-change requires ng-model,
<input type="text" name="abc" class="color" ng-model="someName" ng-change="myStyle={color:'green'}">
I've got the same issue, my model is binding from another form, I've added ng-change and ng-model and it still doesn't work:
<input type="hidden" id="pdf-url" class="form-control" ng-model="pdfUrl"/>
<ng-dropzone
dropzone="dropzone"
dropzone-config="dropzoneButtonCfg"
model="pdfUrl">
</ng-dropzone>
An input #pdf-url gets data from dropzone (two ways binding), however, ng-change doesn't work in this case. $scope.$watch is a solution for me:
$scope.$watch('pdfUrl', function updatePdfUrl(newPdfUrl, oldPdfUrl) {
if (newPdfUrl !== oldPdfUrl) {
// It's updated - Do something you want here.
}
});
Hope this help.
When you want to edit something in Angular you need to insert an ngModel in your html
try this in your sample:
<input type="text" name="abc" class="color" ng-model="myStyle.color">
You don't need to watch the change at all!
Maybe you can try something like this:
Using a directive
directive('watchChange', function() {
return {
scope: {
onchange: '&watchChange'
},
link: function(scope, element, attrs) {
element.on('input', function() {
scope.onchange();
});
}
};
});
http://jsfiddle.net/H2EAB/
One can also bind a function with ng-change event listener, if they need to run a bit more complex logic.
<div ng-app="myApp" ng-controller="myCtrl">
<input type='text' ng-model='name' ng-change='change()'>
<br/> <span>changed {{counter}} times </span>
</div>
...
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.name = 'Australia';
$scope.counter = 0;
$scope.change = function() {
$scope.counter++;
};
});
https://jsfiddle.net/as0nyre3/1/
First at all i'm seing your code and you haven't any controller. So i suggest that you use a controller.
I think you have to use a controller because your variable {{myStyle}} isn't compile because the 2 curly brace are visible and they shouldn't.
Second you have to use ng-model for your input, this directive will bind the value of the input to your variable.

AngularJs Directive to simplify form inputs

My company has theme requirements and has asked me to do some research on AngularJs. To that end, I'd like to show how Angular can be used to implement our theme.
Here is a plunk of what I have so far.
Here is the HTML of what I have that works great.
<form name="frmLogin" ng-submit="submit()" novalidate>
<div class="form-field" data-ng-class="{'error': frmLogin.txtLastName.$invalid && frmLogin.txtLastName.$dirty && !frmLogin.txtLastName.$focused}">
<label for="txtLastName" class="required">Email</label>
<input id="txtLastName" name="txtLastName" type="text" data-ng-model="user.lastName" required autofocus ng-focus />
<div class="inline-validation" data-ng-show="frmLogin.txtLastName.$invalid && frmLogin.txtLastName.$dirty && !frmLogin.txtLastName.$focused">
{{getError(frmLogin.txtLastName.$error,
{
required: "LastName is required"
}
)}}
</div>
</div>
</form>
I'm working on a directive to simplify the HTML for the developer.
Here's my idea.
<form name="frmUserName" novalidate>
<ff-text-input control-name="txtFirstName" control-label="First Name" ng-model="user.userEmail"></ff-text-input>
</form>
Being new to this, I'm struggling with the directive.
I don't know how to grab the whole frmLogin.txtLastName.$invalid && frmLogin.txtLastName.$dirty && !frmLogin.txtLastName.$focused so the developer doesn't have to.
I'm able to determine the form name and control name by using ctrl.$name + "." + scope.controlName but I'm lost on how to apply that to the $invalid etc...
Thanks,
Duane
Since, you want your directive to interact with form states, you need to have a form controller in your directive.
You can do this with
app.directive('inputValidate', function(){
return {
restrict: 'A',
scope: true,
require: '^form',
The require tell Angular that your directive depends on the form, so Angular moves up the DOM, until it finds a form, and when it finds a form, it passes a controller to the directive and you can receive that controller like this.
link: function(scope, element, attrs, formController){
Here formController refers to the controller of the form.
Now, for the your last query, suppose you have an input box with type defined as employeeID, with some custom rule plus juggling, you are able to conclude that input is wrong. To inform Angular that this input is wrong, you can use $setValidity method of the formController.
The syntax is like
formController[controlName].$setValidity(str, value);
where controlName is the name of the control, str is the field for which validation has failed, and value would be either true or false depending upon the your validation state.
Depending upon your validation state and key, Angular will apply fallowing class to the element. For example, if your key in empId and input is invalid, the in class attribute of the element, you can see this.
class = "ng-invalid-empId"
And if it is valid and you have told angular with formController using this.
formController[controlName].$setValidity('empId', true);
You would have something like this.
class="ng-valid-empId"

How can I add a class to an input if the input is changed with AngularJS?

I coded the following in my form:
<td><input type="text" ng-model="row.title" /></td>
When I look at my DOM with Chrome developer tools I see the following:
<input type="text" ng-model="row.title" class="ng-pristine ng-valid">
How can I make it so that when there is a change made to the input that the input has a class added to it?
There are two good ways to approach this problem:
1. Use the built-in ng-dirty class that Angular puts on the element.
When you change an input managed by Angular, it adds some CSS classes to the input for various states. These include:
ng-pristine - the input has not been modified
ng-dirty - the input has been modified
So, if you can modify your CSS to be based off the .ng-dirty class, you're good to go.
2. Use a form directive with the $dirty flag.
When you use a form element, Angular assigns a FormController instance on the scope with the same name as the name attribute on the form; each input inside the form gets attached to that FormController instance as a property, again with the same name as the name attribute on the input. For example,
<form name="myForm">
<input type="text" name="myInput">
</form>
gives you
$scope.myForm.myInput
Each input property has some of its own properties on it, including $pristine and $dirty; these work just like the CSS classes listed above. Thus, you can check for the $dirty flag on the input and use ng-class to conditionally apply a class to the element. An example:
<div ng-controller="MainController">
<form name="myForm">
<input name="myInput" ng-model="model" ng-maxlength="3"
ng-class="{changed: myForm.myInput.$dirty}">
</form>
</div>
You can find a working example here: http://jsfiddle.net/BinaryMuse/BDB5b/
Take a look at this jsfiddle: http://jsfiddle.net/hNrEV/2/
The main idea is using $scope.$watch to watch for changes to the input box. I gave it an id of rowTitle, and used a directive called watchRowTitle that watches for changes to $scope.row.title, and adds a class 'red' that colors the text red whenever the text in the input box is equal to 'wrong title'.
It is probably good practice to do DOM manipulation in directives. Here, the watchRowTitle directive returns an object with 4 keys:
template - the html that replaces the watch-row-title tag. we dont need this here
scope - Here we make use of an isolated scope. Basically, the '=' establishes a 2-way data binding between between scope.title inside the watch-row-title directive and the $scope.row.title value inside the MyCtrl controller.
restrict - We give it a value of E, which stands for element. So this restricts the use of the watch-row-title directive within html tags, in other words: <watch-row-title></watch-row-title>
link - this is the link function, where the interesting stuff happens. In here, we use scope.$watch on title. We have to supply a function with 2 parameters newValue and oldValue (you can name them to something else, but naming them this way is more meaningful), that holds the new and old values of the variable being watched. Whenever the scope.title variable becomes the string 'wrong title', it adds the CSS class 'red' to the input box with id rowTitle (notice how the text in the input box turns red). Otherwise, it removes that CSS class. This portion is done using JQuery.
HTML:
<div ng-app="myApp" ng-controller="MyCtrl">
<input id="rowTitle" type="text" ng-model="row.title" class="ng-pristine ng-valid" />
<watch-row-title title="row.title"></watch-row-title>
</div>
CSS:
.red {
color: red;
}
JavaScript:
angular.module('myApp', [])
.controller('MyCtrl', [
'$scope',
function ($scope) {
$scope.row = {};
}
])
.directive('watchRowTitle', [
function () {
return {
template: '',
scope: {
title: '='
},
restrict: 'E',
link: function(scope, element, attr) {
scope.$watch('title', function(newValue, oldValue) {
if (newValue === 'wrong title') {
$('#rowTitle').addClass('red');
} else {
$('#rowTitle').removeClass('red');
}
});
}
};
}
]);
HTML
<input type="text" id="inputTitle" ng-model="row.title" />
JS
$scope.$watch('row.title', function(newValue) {
// Add CSS class on input
$('#inputTitle').addClass('YourCSSClass');
}, true);

Resources