Form validity using Angular ng-repeat - javascript

I have an Angular form that is parsing some JSON data.
I'm rendering using ng-repeat. However, I'm having an issue in that the form never becomes valid when a radio button in each group is selected.
I suspect the issue lies with the ng-model in each input, but I can't seem to figure out the correct way to dynamically create an ng-model inside an ng-repeat.
Form block code:
<form name="posttestForm">
<alert ng-repeat="alert in alerts"
type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert>
<div class="well question-well" ng-repeat="question in questions">
<p>
<strong>{{question.question}}</strong>
</p>
<ul>
<li ng-repeat="answers in question.answers">
<input ng-model="Q[question.id]"
type="radio" name="Q{{question.id}}" value="{{answers.id}}"
required="" data-key="{{answers.isCorrect}}" />{{answers.answer}}
</li>
</ul>
</div>
<button type="submit" class="btn btn-primary" ng-click="EvaluatePosttest(3)"
ng-show="!TestPassed">
Submit Questions</button>
</form>
Here's a Plunkr that shows the dynamic code and demonstrates the form never turning valid when a radio button in each group is selected.
http://plnkr.co/edit/HQGPIOCdn3TGlE96CpK5?p=preview
And here's a Plunkr using static content displaying it working.
http://plnkr.co/edit/ZFt2VnBfaQjuu73kaNQJ?p=preview

Just add this in your javascript controller
$scope.Q = [undefined,undefined,undefined,undefined,undefined,undefined];
Explanation : you set ng-model as Q[question.id] but Q is undefined so the ng-model won't ever work. You always must initialize variable in the controller. The only case it works not initialize is when you do :
ng-model="test"
if you do
ng-model="test.test"
ng-model="test[0]"
It won't ever work if it's not initialized properly.

You can do a custom form validation inside your controller. In your case:
$scope.Q = [];
$scope.$watch('Q', function (Q) {
$scope.posttestForm.$setValidity('count', Q.length >= $scope.questions.length);
}, true);
Inside that, you can do whatever validation you want.
Here's the working plunkr: http://plnkr.co/edit/7Ww4fjJzkDjifPaZ2QtG?p=preview

Related

ng-click event on html5 datalist not working angularjs

Iam trying an auto search and on selecting a product., I need to redirect to the product URL associated to selected dropdown. Iam able to get all search results.
I have created datalist for all search list and created ng-click event on datalist to send the selected details to controller. But ng-click is not working in datalist. Can you help me out
for.eg. in (key,data) ., I need to show data in front end search box., but on selecting that data from datalist., I need to send its respective key to controller
problematic section for your reference (Full code in plunker):
<h2>Custom search field</h2>
<div id="custom-search-input">
<div class="input-group col-md-12">
<input type="text" class="form-control input-lg" list="suggestions" placeholder="search" ng-model="obj.searchText" ng-focus="searchSuggest()" />
<span class="input-group-btn">
<button class="btn btn-info btn-lg" type="button" ng-click="showProduct(obj)">
<i class="glyphicon glyphicon-search"></i>
</button>
</span>
</div>
</div>
<div>
<datalist id="suggestions">
<p ng-repeat="values in suggestionResults track by $index"><option ng-repeat="(key,data) in values" value="{{data}}" ng-model="selectedProduct" ng-click="showProduct({key: key, data: data})"></p>
</datalist>
</div>
Here in above code., u can see key and data. I need to just show Data value but on selecting one option., i need to send the respective URL link to conroller. Created datalist in ng-repeat
Please select a value from dropdown in textbox from plunker link below
*
I have updated plunker so that URL also can be seen in search
dropdown., I need to pass that URL to conroller in short. please help
me how to achieve it
*
plnker code here
Your ng-click triggered fine in chrome, I can see your console.logs data.
I check your code, the reason that your search logic is not working and you keep getting "not matching" in your console, is that you are using wrong condition, you are not checking the value, with search string, you are checking "Key/Value object" with search string.
change your if condition in showProduct method to this :
if ($scope.suggestionResults[i][Object.keys($scope.suggestionResults[i])[0]] == searchText) {
console.log(Object.keys(response.data[i])[0]);
$scope.redirectLink = Object.keys(response.data[i])[0];
}else{
$scope.redirectLink = "not matching";
}
Check this plunkr
I wrote another method
$scope.inputChanged = function(data){
var obj = _.find($scope.suggestionResults,function(o){
var keyArr = Object.keys(o);
return o[keyArr[0]] === data
})
$scope.showProduct(obj)
}
which will work as a workaround to achieve what you are looking for. From here you can call $scope.showProduct(obj) and achieve ng-click event behavior

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.

Input checked has not working in side partial in angular

I m beginner in Angular. I m working on a angular project. I have a input checkbox in my partial source. When it checked a popup should appear, not checked state have a popup.
I found the below code on google and it works separately. But when i put it my partial source it doesn't work.
<div>
<div >
<input type="checkbox" ng-true-value="A" ng-false-value="B" ng-model="check"/>
</div>
<div ng-show="check == 'A'">
Checked
</div>
<div ng-show="check == 'B'">
Unchecked
</div>
Can you anyone help me
Assuming You want to access the parent controller value inside partial which has been loaded using ng-controller directive, In that you need to follow dot rule in your code, As a ng-controller create a new child scope whenever it adds the specified template.
For solving issue you should have your data in object structure in-order to follow prototypal inheritance
Code
$scope.model = {};
Markup
<div>
<div >
<input type="checkbox" ng-true-value="A" ng-false-value="B" ng-model="model.check"/>
</div>
<div ng-show="model.check == 'A'">
Checked
</div>
<div ng-show="model.check == 'B'">
Unchecked
</div>
Other way to do it would be is you can simply use $parent before the check scope variable, that will refer to the parent scope of the controller.
Just replace check with $parent.check everywhere in the view, This will work but the first approach is most preferable.
Add single quotes to 'A' and 'B'
<div >
<input type="checkbox" ng-true-value="'A'" ng-false-value="'B'" ng-model="check"/>
</div>

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.

Angularjs, checking if radio buttons in form have been selected

I'm starting with AngularJS, and I'm building a multi-step form where user has to fill different pages. When finished a page, he's allowed to press a next button and fill the following page.
For the first page, I've built in the HMTL a form (named pageOneForm), with different text input fields, marked as required, and in the relative controller I'm doing this watch:
$scope.$watch('pageOneForm.$valid', function(validity) {
ModelData.actualPageCompleted = validity;
})
And it works like a charme. My model (ModelData) is updated.
I was trying to apply the same logic to the following part of the app, the second page. Instead of input text, the user has to select two options from 2 different radio buttons groups.
So I built in the html a list of buttons via ng-repeat :
<div ng-Controller="PageTwo" ng-show='data.actualPage == 2'>
<form name="pageTwoForm">
<h3>General Information > Knowledge About </h3>
<div>
<b>User</b>
<div ng-repeat="option in userOptions">
<input type="radio" name="userGroups" ng-model="data.knowledgeAboutUser" ng-value="option.id" id="{{option.id}}" required>{{option.text}}
</div>
<div ng-repeat="option in targetGroupUserOptions">
<input type="radio" name = "targetUserGroup" ng-model="data.knowledgeAboutTargetGroup" ng-value="option.id" id="{{option.id}}" required>{{option.text}}
</div>
</div>
</form>
and I've implemented the same code as above in its controller:
$scope.$watch('pageTwoForm.$valid', function(validity) {
ModelData.actualPageCompleted = validity;
})
but apparently it doesn't work, and in my model actualPageCompleted is always true...
What am I doing wrong?
Thanks
I did my best to create a controller with some dummy data to get a fiddle working with your example code. Here is the fiddle You need to force the $digest cycle to update your form's validity state on ng-click for the radio buttons (see this SO post for more details), which is why the method
$scope.forceDigest = function(){
setTimeout(function(){ $rootScope.$$phase || $rootScope.$apply(); });
};
is necessary. Alternatively, you can get rid of the method call and uncomment the html code
<h3 ng-show="false">{{data.knowledgeAboutTargetGroup}}</h3>
<h3 ng-show="false">{{data.knowledgeAboutUser}}</h3>
in the fiddle to force the form object to update as well.
And I would make sure that ModelData.actualPageCompleted is not retaining its true value from when pageOneForm.$valid became true and it was set.
I hope that this helps!

Categories

Resources