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

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)"

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.

AngularJS form vaidate with radio buttons and ng-repeat

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

Ng-model's attribute in a ng-repeat input checkbox gets always literal or give error

So i need to know the extras of a car than a user wants to include in his preferences.
I'm trying to create input checkboxes from an array obtained by an ajax request and generate the inputs by ng-repeat. The major objective is to know the checkboxes selected by the user. I'd like that my approach to be create an auxiliar array which contains the selected ones, but i don't know how to set a unique ng-model to every item in the ng-repeat iteration so i can know the list of selected items. I guess there is something left in my knowlege of angular. Here is what i have for now..
In the controller...
$http.get('/ajax/ajax_get_extras/'+$scope.car.version+'/false').success(function(data) {
$scope.extras = data;
});
$scope.addExtra = function(){ // ... manage the auxiliar array }
In the html ...
<div ng-controller="Controller">
<form novalidate class="simple-form">
<span ng-repeat="extra in extras">
<input type="checkbox" ng-model="extra.id" ng-change="addExtra()" name="extra_{{extra.id}}" >{{extra.name}} - <strong>{{extra.real_price | onlynumber | currency}}</strong>
</span>
</form>
</div>
And i'm stuck since the extra.id doesnt transform to the real extra.id and stays as a string "extra.id" >_<
I tried extra_{{extra.id}}, extra.id, {{extra.id}}, $index as posibles ng-model and none works.
In AngularJS 1.1.5 there is "track by" that you can use in ngRepeat.
So you can:
<input type="checkbox" ng-repeat="e in extra track by $index" ng-model="extra[$index]">
Here is a example: http://plnkr.co/edit/6lNo6R5EPsNGHUU6ufTE?p=preview

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