AngularJS form vaidate with radio buttons and ng-repeat - angularjs

I'm trying to validate a form that is dynamically generated with JSON data, that is rendered to the page using ng-repeat. The data is questions with corresponding answers. The issue I'm running in to is that with a dynamic ng-model on each group, like so:
<div class="well question-well" ng-repeat="question in Posttest1Questions">
<p><strong>{{question.question}}</strong></p>
<ul>
<li ng-repeat="answers in question.answers">
<input type="radio" name="Q{{question.id}}" ng-model="question_[question.id]" id="{{question.id}}" value="{{answers.id}}" required data-key="{{answers.isCorrect}}">{{answers.answer}}
</li>
</ul>
</div>
Even when all the questions are answered, the form never turns valid. In turn, if I remove the ng-model attr, the form is always valid, even if no radio buttons has been selected.
Example Plunkr: http://plnkr.co/edit/DvcJ8byS0yF7iLp37Ets?p=preview

You can use ng-required to set a condition on the input's required status. In this case, if the model used with ng-model is null, then required. Otherwise, not required.
This way, once you've selected one of the answers (the model has a value), all of the answers for this question will not be marked as required.
<input type="radio" name="Q{{question.id}}" ng-model="question[question.id]" id="{{question.id}}" value="{{answers.id}}" ng-required="question[question.id] == null" data-key="{{answers.isCorrect}}" />
See it working here.

The underscore in
ng-model="question_[question.id]" seems wrong to me.
Please try ng-model="question[question.id]" then you can simply say required
updated your plnkr: http://plnkr.co/edit/gCZHFqd07880Os8FcxhG?p=preview

Related

When ng-init in ng-repeat is replays?

I have a simple ng-repeat to build a HTML list from a javascript array.
Each item can be moved using an input to get the new rank. This input is binded to a variable rank. This variable is initialized using the ng-init directive.
Code looks like this :
<li ng-repeat="item in ctrl.getItems()">
<div ng-init="rank = $index">
[$index: {{$index}}]
{{item}}<br/>
<label>
Move to
<input type="number" ng-model="rank"/>
</label>
<button type="button" ng-click="ctrl.moveItem($index, rank)">
Ok
</button>
</div>
</li>
At runtime, when I change the input value and click to the Ok button, function ctrl.moveItem is called and item is really moved in the ctrl.getItems() array.
So the ng-repeat is replayded and items appears in the new order.
BUT variable rank is not reinitialized and 2 items appears with the same rank.
The sample is here : https://jsfiddle.net/nlips/4ng34b7b/
My question is not so much about moving items in a list, but I need to understand how ng-init works in the context of ng-repeat.
I did not find anything on this subject in the AngularJS official documentation.
From AngularJS docs:
The ngInit directive allows you to evaluate an expression in the current scope.
Now. You are working with different scope.
You are using ngInit into the transcluded scope, overriding $scope.rank each time it repeats that portion of template.
If you want to persist your rank you should init it into the ngRepeat scope.
Try with:
<li ng-repeat="item in ctrl.getItems()" ng-init="rank = $index">
[$index: {{$index}}]
{{item}}<br/>
<label>
Move to
<input type="number" ng-model="rank"/>
</label>
<button type="button" ng-click="ctrl.moveItem($index, rank)">
Ok
</button>
</li>
EDITED ANSWER
Ok, i got it.
The ngInit expression is evaluated only when that part of template is going to be rendered into the DOM.
So, when the page is loaded for the first time your expression is fired and each rank is evaluated correctly.
But, when you make changes on an item that is already rendered, your template is not going to be rendered again, so your ng-init will not be fired.
If you want that ng-init to be executed again you have to remove the item from the DOM and then append it back, into the new position.
There are several alternatives to this approach, but i hope this clarifies what was going on.

What magic does ngModel use that modifying the scope directly does not?

Learning angularjs at the moment, and I am confused as to how I can accomplish a task because I don't fully understand what ngModel is doing.
If I have a directive with two scope variables:
// An array of all my objects
$scope.allMyObjects
// The currently selected object from the array
$scope.selectedObject
and in the html
<span>{{ selectedObject.name }}</span>
<select id="select"
ng-model="selectedObject"
ng-options="object in allMyObjects">
</select>
This all works perfectly, when I select an object from the select, it updates the selectedObject on the scope and so the name of the currently selected object is displayed.
However, I don't want a select box, instead I want a list of all my objects with an editable name field, with a select button that I can use to select the specified object, so I came up with the following:
<div ng-repeat="object in allMyObjects">
<input class="object-name"
ng-model="object.name">
<a ng-click="loadObject(object)">Load</a>
</div>
and the loadObject() function on the scope:
function loadObject(object) {
$scope.selectedObject = object;
}
However, this doesn't work. I had assumed this was basically what ngModel was doing behind the scenes but am obviously confused. Is anyone able to shed some light or offer a better solution to what I wish to achieve?
Please see here :http://jsbin.com/jocane/1/edit?html,js,output
use ng-model="object.name" instead "sc.name"
<div ng-repeat="object in allMyObjects">
<input class="object-name"
ng-model="object.name">
<a ng-click="loadObject(object)">Load</a>
</div>
After an hour of debugging it came down to an issue with the scope being isolated by the ng-repeat, the problem didn't show up in any of the simplified jsfiddle examples because they used pure JS and it was the way I was accessing the scope via typescript that caused the issue.
Thanks for the answers that helped me narrow it down to my difficulty understanding typescript and not my difficulty understanding directives.

How can I reference bound data within a ng-click attribute?

It's been a long day, and I am likely missing something obvious but. . .
I have a ng-repeat list of radio buttons that reflects live UPS shipping rates to a customer's location (e.g. "Overnight", "2 day", etc).
Here is the markup:
<div ng-repeat="i in ShippingRates">
<div class='radio'>
<label>
<input value="{{i.Rate}}" ng-model="Cart.ShippingCharge" type="radio" name="shipping-method" ng-click="SetSelectedMethod('Other')" />{{i.ShippingMethod}} <span ng-if="i.Rate > 0">({{i.Rate | currency}})</span></label></div>
</div>
I already have the ng-model of the radio button associated with i.Rate returned from UPS (e.g. $100).
Now, I additionally need to store the text of the radio button as the "SelectedShippingMethod". I thought the way to do this might be to add a ng-click like this:
<input value="{{i.Rate}}" ng-model="Cart.ShippingCharge" type="radio" name="shipping-method" ng-click="SetSelectedMethod('{{i.ShippingMethod}}')" />
And then the function would be something like:
$scope.SetSelectedMethod = function(method){
$scope.SelectedShippingMethod = method;
}
But this does not work as it results in $scope.SelectedShippingMethod literally getting set to "{{i.ShippingMethod}}".
Any help is appreciated.
the function inside the ng-click is already a JavaScript method, not an HTML snippet, so you don't have to evaluate the variable as an expression. Simply call the variable.
e.g. ng-click="SetSelectedMethod(i.ShippingMethod)"

How do I access the child ngModel from a directive?

Like in this question, I want to add .error on a form field's parent .control-group when scope.$invalid is true.
However, hardcoding the form name like in ng-class="{ error: formName.fieldModel.$invalid }" means that I can't reuse this in different forms, plus I'd rather not repeat this declaration everywhere.
I figured that a directive that looks something like this could work:
<div class="control-group" error-on="model1, model2">
<input ng-model="model1">
<input ng-model="model2">
</div>
So when either model1 or model2 is not valid, .control-group gets .error added.
My attempt here. Is it possible to access the models from the directive, given the model names?
If there's a better approach, I'd love to hear it too.
I don't think that writing a custom directive is necessery for this use-case as the ng-form directive was created exactly for situations like those. From the directive's documentation:
It is useful to nest forms, for example if the validity of a sub-group
of controls needs to be determined.
Taking your code as an example one would write:
<div class="control-group" ng-class="{ error: myControlGroup1.$invalid }>
<ng-form name="myControlGroup1">
<input ng-model="model1">
<input ng-model="model2">
</ng-form>
</div>
By using this technique you don't need to repeat expressions used in ng-model and can reuse this fragment inside any form.
You can also change the markup in the accepted answer to do without the nesting, since ng-form is also a class directive:
<div class="control-group ng-form" name="controlGroup11" ng-class="{ error: controlGroup1.$invalid }>
<input ng-model="model1">
<input ng-model="model2">
</div>
Final solution Fiddle
Inside your link function, you can get access to the formController. It has all of the controls. So the following will give your directive access to .$valid:
el.controller('form')[attrs.errorOn].$valid
However, I don't know how to watch that for changes. I tried watching attrs.errorOn (i.e., watch the ng-model property), but the watch doesn't trigger unless a valid value is input (because of the way Angular forms work... unless that value is valid, it is not assigned to the scope property set by ng-model.)
Fiddle.
Maybe someone can take this further...

Angular databinding as a function argument not working

<input type="text" value="{{codes[0].code}}" ng-click="newNumber(0)" />
<input type="text" value="{{codes[1].code}}" ng-click="newNumber({{codes[1].id}})" />
The first ng-click event fires in my controller just fine but the second one does nothing.
I tried concat'ing as well ... is there some other way I should do this?
ng-click
The value of ng-click is already evaluated as an angular expression. As such, you don't need the {{ }}. Read http://docs.angularjs.org/guide/expression for more information. Take a look at the second example, it will help clarify this.
ng-model
Also, ng-model should be used for data-binding. For example, take a look at this jsfiddle: http://jsfiddle.net/bCpW9/8/ and the notes below.
<li ng-repeat="code in codes">
This loops through the codes collection which was defined in the controller. It creates a <li> for each element in the codes collection.
<input ng-model="codes[$index].code" />
Inside each <li>, an <input> for the current code is created. Each input is bound to it's corresponding element in the codes array by setting ng-model to it. For instance, type a new code into the first input field. It automatically updates the corresponding code model with what you typed, as you can see to the right.
I hope that helps.

Resources