Form change and validity in angularjs - javascript

Whenever any non-readonly input in my form changes and the form is valid, I want to do certain action. Let's say, my form looks like
<form name="form" novalidate>
<input ng-model='input.a' required/>
<input ng-model='input.b' required/>
<input value='{{output.p | number: 2}}' readonly/>
<input value='{{output.q | number: 2}}' readonly/>
</form>
Now upon any change of input, I want to do something, whenever input.a and input.b are valid. I tried $watch(input), but it didn't work. Watching all its members does, but it feels stupid. Adding ng-change to all fields feels better, but still pretty stupid (non-DRY). What's the proper way?
The other question is how to find out if the input is valid. If I had a button, I could do simply
<button ng-click="doIt()" ng-disabled="form.$invalid">
but how can I access form.$invalid in the controller (it's not contained in $scope)?

You should be able to access form.$invalid by doing
$scope.form.$invalid
See here: AngularJs can't access form object in controller ($scope)
To watch for changes in the form, you should be able to do:
$scope.$watchCollection('input')

#dave has already answered your first question, but for the second I have a solution that I consider more elegant:
In your controller you have declare an object, for example:
$scope.forms = {};
Then you form name must be nested inside that object:
<form name="forms.someForm">
...
Finally in your controller you can do things like this:
if($scope.forms.someForm.$invalid) {
...
}

Related

Disabling submit button based on fields added with ng-bind-html

JSFiddle here: http://jsfiddle.net/c6tzj6Lf/4/
I am dynamically creating forms and buttons and want to disable the buttons if the required form inputs are not completed.
HTML:
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
JavaScript:
angular.module('choicesApp', ['ngSanitize'])
.controller('ChoicesCtrl', ['$scope', '$sce', function($scope, $sce) {
$scope.custom = "Required Input: <input required type='text'>";
$scope.trustCustom = function() {
return $sce.trustAsHtml($scope.custom);
};
$scope.buttons = [
{text:'Submit 1'},
{text:'Submit 2'}];
}]);
choicesForm.$invalid is false and does not change when entering text into the input field.
Solution:
I ended up using the angular-bind-html-compile directive from here: https://github.com/incuna/angular-bind-html-compile
Here is the relevant bit of working code:
<ng-form name="choicesForm">
<div ng-if="choices" bind-html-compile="choices"></div>
<button ng-click="submitForm()" ng-disabled="choicesForm.$invalid">
Submit
</button>
</ng-form>
And choices might be a snippit of HTML like this:
<div><strong>What is your sex?</strong></div>
<div>
<input type="radio" name="gender" ng-model="gender" value="female" required>
<label for="female"> Female</label><br>
<input type="radio" name="gender" ng-model="gender" value="male" required>
<label for="male"> Male</label>
</div>
The main problem is that ngBindHtml doesn't compile the html - it inserts the html as it is. You can even inspect the dynamic input and see that it doesn't have the ngModel's CSS classes (ng-pristine, ng-untouched, etc) which is a major red flag.
In your case, the form simply doesn't know that you've added another input or anything has changed for that matter. Its state ($pristine, $valid, etc) isn't determined by its HTML but by the registered NgModelControllers. These controllers are added automatically when an ngModel is linked.
For example this <input required type='text'> won't affect the form's validity, even if it's required, since it doesn't have ngModel assigned to it.
But this <div ng-model="myDiv" required></div> will affect it since it's required and has ngModel assigned to it.
The ngDisabled directive on your buttons works as expected since it depends on the form's $invalid property.
See this fiddle which showcases how ngModel registers its controller. Note that the html containing the dynamic input gets compiled after 750ms just to show how NgModelControllers can be added after FormController has been instantiated.
There are a few solutions in your case:
use a custom directive to bind and compile html - like this one
use ngInclude which does compile the html
use $compile to compile the newly added HTML but this is a bit tricky as you won't know exactly when to perform this action
This is an answer yet imcomplete because i cannot do the code at the moment.
I think your html will be included, not compiled. So the inputs are not bind to angular and are not part of the angular form object.
The only way i see is to use a directive that will compile the passed html and add it to your form. This may be quite tricky though, if you want to go on this way i suggest to edit your question to ask for the said directive.
However i'm not really familiar with $compile so i don't know if it'll work to just add $compile around $sce.trustAsHtml()
You can write a method as ng-disabled does not work with booleans, it works with 'checked' string instead:
So on your controller place a method :
$scope.buttonDisabled = function(invalid){
return invalid ? "checked" : "";
};
And on your view use it on angular expression :
<button ng-repeat="button in buttons" ng-disabled="buttonDisabled(choicesForm.$invalid)">
Here is a working fiddle
Working DEMO
This is the solution you are looking for. You need a custom directive. In my example I have used a directive named compile-template and incorporated it in div element.
<div ng-bind-html="trustCustom()" compile-template></div>
Directive Code:
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I found the directive in this fiddle.
I believe what is really happening though due to jsfiddle I'm unable to dissect the actual scopes being created here.
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
The first div is your top level scope, your form is the first child scope. Adding the div using a function creates the dynamically added input field as a child of the first child, a grandchild of the top level scope. Therefore your form is not aware of the elements you're adding dynamically causing only the static field to be required for valid form entry.
A better solution would be to use ng-inclue for additional form fields or if your form isn't to large then simply put them on the page or template you're using.

How to let Angular JS ng-init only under certain condition?

Let say I have the following short input form:
<form ng-controller="parentController" action="testing.php" method="post">
<input name="parentInputField" ng-model="x">
<div ng-controller="childController">
<div ng-repeat="item in itemList">
<input name="childInputField[]" ng-model="x">
</div>
</div>
</form>
As you may already know, the model x in childController will follow the value of of model x in parentController until the user type in something in the childController input field, so the user can simply change the value in parent for bulk input and then fine tune in child.
Now, after the user have submitted the form, I want to call the same form for editing function. To keep the bulk input function on new items, is there a way I can ng-init model x in childController only when there is a previous value?
Haven't tried but I believe you can achieve with:
<div ng-init="ctrl.value && (ctrl.value=1)">
BUT if you want an advice, avoid both ng-init and nesting controllers like this: it would be a pain to maintain this program. Prefer to use controllerAs syntax (https://docs.angularjs.org/api/ng/directive/ngController) and put init logic on controller/service.

HTML input has both {{}} and ng-model

I have a form which contains two input fields, I want to sync the next input field when user is typing in the first input field by default, and user can edit the second field as they like, below code works fine:
<input type="text" ng-model="name">
<input type='text' value='{{name}}'>
<button ng-click='submit()'>submit</button>
However, to be able to get the value of second field, I need to put ng-model to the second field, and once I put ng-model, it won't sync anymore.
This is the example
How should I get the second field's value if I don't put a ng-model to it.
Thank you.
You can use ng-change. When user changes input 1 the ng-change method will be called and input2 will be updated .but when user change input 2 nothing will be called .
<DIV ng-app='app'>
<form ng-controller='myController'>
<input type='text' ng-model='name' ng-change="callMe()"/>
<input type='text' ng-model="name2" />{{name2}}
</form>
</DIV>
and controller js
var app = angular.module('app', []);
app.controller('myController', function($scope){
$scope.callMe =function(){
$scope.name2=$scope.name;
}
//$scope.name
//$scope.name2
})
update fiddle example
You could use the $scope.$watch Method for your needings.
I have updatet your fiddle.
What i did is easy, everytime the model changes, the $watch will be called with the new value of the model, then you just need to copy the value into the second model.
First approach that came to my mind was to ng-bind a property with a setter that would do what you want:
<input type='text' ng-model='nameModel' />
then
app.controller('myController', function($scope) {
Object.defineProperty($scope, 'nameModel', {
get: function() { return $scope.name1; },
set: function(x) { $scope.name1 = $scope.name2 = x; }
});
});
This causes any edit to nameModel to affect both name1 and name2.
Fiddle
You could do a similar wrapper around name2 to track whether it has ever been modified (dirty/pristine), and have nameModel only change scope.name2 if it is pristine.
Fiddle with better user experience
(If you're using Angular forms, it tracks the field status for you, so you could directly look at form.name2.$pristine.)
It seems that what you need is to set the value to the scope variable.
So your code would then look like this:
<input type="text" ng-model="name">
<input type='text' value="{{name}}" ng-model="somethingelse">
<button ng-click='submit()'>submit</button>
Hope that helps.

Cannot read property 'selection' of undefined in AngularJS

I am having troubles linking the inputs of my form into a defined factory that I have injected into the controller that renders the form. Here is my factory:
App.factory('DeviceSelection',function() {
var states=[{selection:{}},{selection:{}},{selection:{}},{selection:{}}];
return states;
});
And here is an input of my form:
<div class="controls">
<label class="radio">
<input type="radio" name="user[role]" id="user_role_managing_editor" value="Managing editor" ng-model='states[0].selection.hours'>
Yes
</label>
<label class="radio">
<input type="radio" name="user[role]" id="user_role_area_editor" value="Area editor", ng-model='states[0].selection.hours'>
No
</label>
</div>
So, when I try to click on that Radio box, I see the following in the JS Console:
TypeError: Cannot read property 'selection' of undefined
Does that mean that I need to initialize the model before the view is rendered. If so, where?
I am trying to achieve a multi-step form, linking all the inputs in the model, until last step is reached when I am able to send the results to an API. As asked here:
Store status between forms in AngularJS?
You say that you have injected in your controller. It would be nice to see that injection, but let me blind guess something that might be happening:
I am assuming you have (something like) this:
YourApp.controller('YourController', ['$scope', 'YourFactory', function ($scope,$yourFactory) {
...
But, have you set that injection into the $scope? Otherwise the view won't have access :)
So, if you don't have it, do this:
$scope.states=$yourFactory;
I really believe this is what happened. The controller needs to tell the view where to find that state array through the $scope

Populate form field with javascript

I have a form with several fields populated by the user and before it is submitted some javascript gets called when a check button. It tries to set the value of the form fields to a variable that exists in the js function.
document.getElementById('var1').innerHTML = test;
alert(test);
I know the javascript is working as expected because I see the alert but the form boxes are not getting populated:
#helper.input(testForm("var1")) { (id,name,value,args) => <input type="text" name="#name" id="#id" #toHtmlArgs(args)> }
innerHTML is used to get/set the body of an html tag, so you're probably ending up with this in the html:
<input ...>test</input>
I think this may work for a <textarea>, but for your <input type="text"> you want to set the value attribute.
document.getElementById('var1').value = test;
If you want to programmatically set an html form field via JS there are many ways to do this and many libraries out there that make it really easy.
Such as various JS two-way component template binding libraries.
For instance, you can simply do the following:
HTML:
<div id="myapp">
<input id="var1"/>
<button>Submit</button>
</div>
JS:
mag.module('myapp',{
view : function(state){
var test= 'tester';
state.button= {
_onclick:function(){
state.var1=test
}
}
}
});
Here is working example of the above example:
http://jsbin.com/ciregogaso/edit?html,js,output
Hope that helps!

Categories

Resources