ng-model and form inside ng-repeat - angularjs

I am creating a form for each item in my $scope. ng-model is not linking with the data on my form submit.
<li ng-repeat="item in favourites">
<form ng-submit="DeleteFavourite()" class="form-horizontal" id="frmSpec">
<input ng-model="item.Description"/>
<input ng-model="item.Refno"/>
<button type="submit"class="btn">{{item.Description}}
<span class="glyphicon glyphicon-remove"></span>
</button>
</form>
</li>

The issue is very closely related to the comment by #DavidBeech . In an angular controller the scope is seen as a hierarchy object.
So for example if you have the following:
<div ng-controller="SomeCtrl">
<li ng-repeat="item in favourites">
<form ng-submit="DeleteFavourite()" class="form-horizonatal" id="frmSpec">
<input ng-model="item.Description"/>
<input ng-model="item.Refno"/>
<button type="submit"class="btn">{{item.Description}}
<span class="glyphicon glyphicon-remove"></span>
</button>
</form>
</li>
</div>
When that controller is injected into the div and that new scope instance is created it only sees what is at that level and the scopes of it's parents. It has no knowledge of its children's scopes. Therefore, when you call DeleteFavourite() since it is a method attached to the scope of the controller it will not have the context of the ng repeat. So as David stated you will need to do something like DeleteFavorite(item) in order for it to have knowledge of what you are submitting otherwise you will not have knowledge of what item in the iteration you are submitting.
Feel free to comment if you want an example and I can put together a fiddle with an example of scope inheritance.

Related

Validate form using dynamically textarea name

I have one validation form using angular. I would like validate forms in ng-repeat using dynamically textarea name. Look on my code:
<div ng-repeat="comment in comments track by $index">
<form name="main.adminArticleReplyForm" novalidate>
<div ng-class="{'has-success':(main.adminArticleReplyForm.commentReply_{{$index}}.$valid), 'has-error':(!main.adminArticleReplyForm.commentReply_{{$index}}.$valid)}">
<textarea name="commentReply_{{$index}}" ng-model="main.commentReply[$index]" required></textarea>
<ul ng-show="(!main.adminArticleReplyForm.commentReply_{{comment.number}}.$pristine && main.adminArticleReplyForm.$error.minlength[0].$viewValue.length)">
<li>You have to add {{10 - main.adminArticleReplyForm.$error.minlength[0].$viewValue.length}} characters</li>
</ul>
<button type="submit">Submit</button>
</div>
</form>
</div>
And my problems:
If you check {{main.adminArticleReplyForm}} commentReply_{{$index}} is always last $index in ng-repeat for example if are 4 comments in array in form always is commentReply_4, but textareas name in DOM are correct.
I have to use controller as, in my case main, but if I remove main from form name, everything works nice.
Summary, {{main.adminArticleReplyForm}} is validate only for last $index in ng-repeat previously $indexes are ignored
Here is fiddle for look that every commentReply_{{$index}} is commentReply_2 https://jsfiddle.net/8od25zhb/5/
Description
you don't need to track it by index that make repeat last index
you don't need to add main to the form, because form is in repeat so make it unique, if you want to have just one form make it out of repeat; so we just use comment.adminArticleReplyForm as form name, and just main.comments used main from controller.
by this 2 step your forms validate just the textarea which inside it.
you don't need create unique names and models for textarea, because you create them inside ng-repeat and all are unique.
<div ng-app="app" ng-controller="ctrl as main">
<div ng-repeat="comment in main.comments">
<form name="comment.adminArticleReplyForm" novalidate>
{{comment.adminArticleReplyForm}}
<div>
<textarea name="commentReply" ng-model="comment.commentReply" required></textarea>
<button type="submit">Submit</button>
</div>
</form>
<hr/>
</div>
</div>
Since, AngularJS default validation works on hierarchy of element names to detect their state thus, the name of textarea you have used is commentReply_{{$index}} with a underscore but you are using commentReply{{comment.number}} in the validation expressions. So make it look with a underscore and it will work. So, final code will look something like this:
<div ng-repeat="comment in comments track by $index">
<form name="main.adminArticleReplyForm" novalidate>
<div ng-class="{'has-success':(main.adminArticleReplyForm.commentReply_{{$index}}.$valid), 'has-error':(!main.adminArticleReplyForm.commentReply_{{$index}}.$valid)}">
<textarea name="commentReply_{{$index}}" ng-model="main.commentReply[$index]" required></textarea>
<ul ng-show="(!main.adminArticleReplyForm.commentReply_{{comment.number}}.$pristine && main.adminArticleReplyForm.$error.minlength[0].$viewValue.length)">
<li>You have to add {{10 - main.adminArticleReplyForm.$error.minlength[0].$viewValue.length}} characters</li>
</ul>
<button type="submit">Submit</button>
</div>
</form>
</div>

Custom validation via child control

So I'm working inside a directive that contains it's own form element and buttons, however all the controls must be transcluded through. The model for this particular view contains a property for total capacity, and a property that is a collection of compartments (separate entity). Each compartment has it's own capacity. I already have a function that will show an error on the view if/when the Total Capacity is not equal to the combined capacity of all compartments. The problem here is, since all my controls are transcluded through (and I'm not supposed to modify the parent directive) I have no clue if/how I can use that same function to mark the form as invalid to disable the save button. I was wondering if there is a solution (hopefully one that doesn't involve custom directives or services) that would allow me to set the parent form invalid if an expression returns true.
** UPDATE **
Sorry guys, I think I explained it backwards the first time. So this would be a good representation of what is going on in the html. (Also I haven't used stackoverflow much before this so bear with me)
edit-page-directive:
<div>
<form name="editForm">
<ng-transclude>
</ng-transclude>
<a class="btn btn-success">Save</a>
<a class="btn btn-danger">Cancel</a>
</form>
</div>
View for this particular edit:
<edit-page>
<uib-tabset>
<uib-tab>
<!--Total Capacity input-->
<input type="text" numeric="{min:1, format:'#,###.#'}" ng-model-options="{updateOn: 'blur'}" class="form-control" id="tcCapacity" name="tcCapacity" data-ng-required="true" ng-model="vm.dataContext.entity.TotalCapacity" />
<!--End Total Capacity-->
</uib-tab>
<uib-tab>
<table>
<tr><thead><th>...</th><th>Capacity</th><th>(Buttons for compartment add/remove)</th></thead></tr>
<tr ng-repeat="compartment in vm.dataContext.entity.TrailerConfigCompartments">
<td width="200">{{compartment.Sequence}}</td>
<!--Important input under this-->
<td><input type="text" numeric="{min:0, format:'#,###.#'}" class="form-control" ng-model="compartment.Capacity" data-ng-required="true" /></td>
<!--Important input above-->
<td align="right" style="padding-right:30px;">
<a class="btn" style="padding: .7em; color: black;" ng-click="vm.addCompartment(compartment.Sequence + 1)">
<span uib-tooltip="New compartment at sequence {{compartment.Sequence + 1}}" class="btn-edit" style='margin-left:5px'><span class="glyphicon glyphicon-plus" style="margin-top:3px"></span></span>
</a>
<a class="btn" style="padding: .7em; color: black;" ng-click="vm.removeCompartment(compartment)">
<span uib-tooltip="Remove compartment" class="btn-edit" style='margin-left:5px'><span class="glyphicon glyphicon-minus" style="margin-top:3px"></span></span>
</a>
</td>
</tr>
</table>
</uib-tab>
</uib-tabset>
</edit-page>
If I understand you correctly you have something like
HTML
<div data-ng-controller="FormController as vm">
<form class="foo form">
<input type="text"> // some inputs
<input type="text"> // some inputs
<transcluded-directive>
<button class="foo button-to-disable">Do something</button> // button that should be disabled
</transcluded-directive>
</form>
</div>
JS
.controller("FormController", function($scope) {
var vm = this;
vm.validateTotalCapacity = function () {
// validation stuff
}
});
So I think you can do something like:
HTML
<div data-ng-controller="FormController as vm">
<form class="foo form {{vm.validateTotalCapacity() ? '' : 'form-has-errors'}}" >
<input type="text"> // some inputs
<input type="text"> // some inputs
<transcluded-directive>
<button class="foo button-to-disable">Do something</button> // button that should be disabled
</transcluded-directive>
</form>
</div>
Look I put your form validator in <form class="foo form"> and make condition for error class
CSS
.form-has-errors .button-to-disable {
pointer-events: none;
cursor: default;
opacity: 0.5
// or your custom disabled styles
}
UPDATE
I see, but I believe you could try this:
HTML
<div>
<form name="editForm" class="{{editForm.$valid ? '' : 'form-has-errors '}}">
<ng-transclude>
</ng-transclude>
<a class="btn btn-success">Save</a>
<a class="btn btn-danger">Cancel</a>
</form>
</div>
So I realized I had misinterpreted the customValidation piece of angularjs. I thought any directive I'd have to create for validation would have to be added to the form element itself. Just as well I thought it would be alot harder to set up than it actually is.
For future reference:
1.) Create a directive and restrict it to an attribute
2.) Require ngModel for this directive
3.) Set up your link function:
link: function(scope, elem, attrs, ngModel) {....}
4.) Add a function to the $validators object of the control you want to validate. Do this INSIDE of your link function. Ex:
link: function(scope, elem, attrs, ngModel) {
ngModel.$validators.validationFn = function(value) {
//Where value is the current value of the control
//In my case, where I want to compare value to the combined value of other
//compartments I would send in whatever data I wanted via the scope property of
//this directive and compare the two in this function
}
}
5.) Return true if control is valid and vice versa
And that's it.
If you want to access this validator to display an error message just:
ng-show="vm.arbitraryInput.$error.validationFn"
Keep in mind that now if it returns true, then the input is invalid.

angularjs: dynamically create btn-grp

a backend defined datatypes, which I should render dynamically in angularjs. The backend might define "sex is enumeration, values male and female".
{
'name': 'sex',
'type': 'enum',
'options': ['female','male'],
'wert': 'male'
}
I can quite easily use a select:
<select ng-model="wert" ng-options="value for ( key,value) in options"></select>
But would prefer a button group like
<div class="btn-group">
<label class="btn btn-primary" ng-model="wert" uib-btn-radio="'male'">male</label>
<label class="btn btn-primary" ng-model="wert" uib-btn-radio="'female'">female</label>
</div>
Now I try to have those buttons dynamic...
<div class="btn-group" ng-repeat='option in options'>
<label class="btn btn-primary" ng-model="wert" uib-btn-radio="'{{option}}'">{{option}}</label>
</div>
And I fail. Looked quite straight forward to me. But why does the dynamic button group not work out? Seems not to be connected to my scope?
I'm probably not grasping some fundamental angular concept here?
The code is to be put in a directive, but that's probably not related to the issue.
Any help would be greatly appreciated.
You should not use {{}} interpolation with the values
<div class="btn-group" ng-repeat='option in options'>
<label class="btn btn-primary" ng-model="model.wert" uib-btn-radio="option">{{option}}</label>
</div>
Other thing I wanted to point out is, you need to define model to follow Dot Ruleso that will ensure binding will work in correct way. The reason behind you should go for Dot Rule is, you have defined ng-model inside ng-repeat, so that would not available in outer context(ng-repeat does create new child scope which is prototypically inherited from it current running controller).
Demo here

Angular UI-Bootstrap dropdown button on ng-options

<select class="form-control" data-ng-options="t.name for t in vm.types"
data-ng-model="vm.object.type"></select>
The code above obviously displays a basic dropdown in a standard form-control manner. I've been trying to figure out how to convert this to a button style dropdown using Angular's ui-bootstrap directives but can't seem to get anywhere. Has anyone tried this?
I hope you already found the answer, but maybe someone else will find this useful. The previous answer refers to a common drop down not a button drop down. Here is an example, but without the benefits of hg-option while I didn't use a select but a button.
<div class="input-group">
<div class="input-group-btn" ng-class='{open: open}'>
<button class="btn dropdown-toggle"
data-toggle="dropdown"
ng-click='open=!open'>
Action<span class="caret"></span></button>
<ul class="dropdown-menu">
<li ng-repeat="choice in choices"
ng-click="setChoiceIndex($index);$parent.open =!$parent.open">
{{choice}}</li>
</ul>
</div>
<input type="text" ng-model="choices[index]" class="form-control">
</div>
where choices is an array of strings that will be displayed in the drop down and index is another variable in the controller scope that will reflect the selected choice.
I have created a basic demo of a drop down using angular bootstrap..
Visit :http://plnkr.co/edit/Mfw5zABqPTgLL4DAgAA3?p=preview
Hope this is what your are looking for.

Angular.JS: why can't the inputs be edited?

This is a strange problem. The code is simple:
HTML code:
<body ng-controller="MainCtrl">
<ul ng-repeat="name in names">
<input type="text" ng-model="name" />
</ul>
</body>
Angular code:
app.controller('MainCtrl', function($scope) {
$scope.names = ["aaa","bbb","ccc"];
});
The live demo url is: http://plnkr.co/edit/2QFgRooeFUTgJOo223k9?p=preview
I do not understand why the input controls can not be edited, I can't type new characters or delete characters.
This is a common issue due to scope inheritance . Each of your names is a primitive so ng-repeat makes it's own scope item that is not connected to original, however if each names is an object ng-repeat scope item will be a reference to original object
[{name:"aaa"},{name:"bbb"},{name:"ccc"}];
Always use a dot in ng-model is a helpful rule of thumb
<div ng-repeat="item in names">
<input type="text" ng-model="item.name"/>
</div>
Working Plunker
Read this article on angular github wiki for detailed explanaton:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
Angular 'fixed' this in 1.1 with the track by $index. No need to change your model.
<div ng-repeat="item in names track by $index">
<input type="text" ng-model="names[$index]" />
</div>
Plunker here
Late answer, but you should also be careful of typos, that angular will not warn you about:
<div ng-repeat="item in names track by $index" ng=if="names[$index] = 'John'">
<input type="text" ng-model="names[$index]" />
</div>
Note the single equals in the ng-if, that will not cause a warning or error, but the text will also be read only. Quite a hard one to spot if you are reading quickly.
It of course should be:
<div ng-repeat="item in names track by $index" ng-if="names[$index] == 'John'">
<input type="text" ng-model="names[$index]" />
</div>

Resources