How to get the NgModelController for input fields inside a ngRepeat directive? - angularjs

Normally, with a form and input fields, the form controller is published into the related scope under the form name attribute. And, the NgModelController is published under the input name attribute.
So for an input field with an ngModel directive, the NgModelController for the input field can be retrieved like $scope.myFormName.myInputFieldName
The question is how to do the same thing (get the NgModelController) for input fields inside the ngRepeat directive?
I would like to name the input fields using $index as part of the name so each template instance is uniquely named. This renders OK, so
<input name="foo_{{$index}}" ...
renders the instance with $index == 3 to
<input name="foo_3" ...
But trying to get the ngModelController via the published names does not work (it's undefined), e.g.:
$scope.myFormName.foo_3
A plunker showing this is here: http://plnkr.co/edit/jYDhZfgC3Ud0fXUuP7To?p=preview
It shows successfully getting the ngModelController for a 'plain' input element and calling $setValidity, and also shows failing to get the ngModelController for an input element inside an ngRepeat directive.
Copied the relevant section of code from the plunker below:
<div ng-repeat="element in elements">
<div ng-class="{error: form['foo_{{$index}}'].$invalid}">
<input name="foo_{{$index}}" ng-model="element.a" type="number">
<span ng-show="form['foo_{{$index}}'].$error.bar">ngRepeat bar invalid</span>
</div>
</div>
{{form.foo_0.$setValidity('bar', false)}}

#Flek is correct that the new child scopes that ng-repeat creates are the root of the problem here. Since browsers do not allow nesting of <form> elements, ngForm must be used when nesting forms, or when you want to do form validation inside ngRepeat.
See Pawel's answer on the google group thread, which shows how to use ng-form to create inner forms, and/or #blesh's SO answer.

If i understand your question correctly, you are trying to have access to the form elements created inside the ng-repeat.
Please have a look at this fiddle http://jsfiddle.net/EF5Jp/. Inside the button click handler you will have the access to the element with id myForm.foo_2. You can notice that the element is retrieved by myForm.foo_2 and not $scope.myForm.foo_2. Second thing is, changing the value using its scope and not using its value property like angular.element(element).scope().foo = 6;.

Related

Represent ng-model in a controller

I am developing application using AngularJS and ASP.Net MVC...
In my view, I have used ng-repeat=u in users in a div. Inside it, I set the ng-model in a textbox as ng-model=u.fName where fName is database field.
My question is, how I can represent the ng-model=u.fName in the angular controller??
For example, I tried to use $scope.n.fName but it doesn't work.
Note that u is only existing in ng-repeat block.
If you want to show/access to value changed by textbox outside of block, you should access via users again with another ng-repeat block:
<li ng-repeat="u in users">{{u.fName}}</li>
Or add ng-change="change(u)" to your textbox to get u anytime it's changed.
<input ng-model="u.fName" ng-change="change(u)" />
Plunkr example

How to use FormController with directives

I'm trying to build widgets directives that play well with forms
The complete example is available at http://jsfiddle.net/jy81bchd/4/
The main idea is:
Write your own form, put widget in it
<form name="otherForm" novalidate ng-init="model = {name: 'test'}" novalidate>
<swif-widget-text name="name" required="required" input-model="model.name">
<span translate>Name</span>
</swif-widget-text>
</form>
The widget directive copy all attributes to the input and transculde the contennt in the the label.
My differents problems are:
1) I can't update the formController of the main form, it doesn't find the different inputs throw directives. The best should be to use formcontrolller.addControl but I don't have succeed with it
2) To work around 1 I have tried to make each widget a different form. This is working execpt formcontroller doesn t update after link has been called (attributed copied to input doesn't affect the controller).
In the fiddle I copied required attribute to the input but if you empty the field it's still valid according to the formcontroller.
I have added name="input" also because if I copy name attribute to the input the form controller doesn't find any input.
Conclusion:
From what I understand formcontroller is initialized and loaded before the link is called.
How could I change that ?
There are several posts about how to create your own form component that plays nicely with the form and the controller that is attached to that.
For instance: http://blog.revolunet.com/blog/2013/11/28/create-resusable-angularjs-input-component/
Examining your fiddle I have a few things you need to look at:
1) add a name and a controller to the form element in order to work with all form elements that live inside of it. Via the $scope that is injected in that controller you can handle the form elements (also add meaningfull names to your form elements).
2) And your custom component:
- should use ng-model instead of input-model.
- should require 'ngModel' in the Directive definition
- in the link phase you get a reference to the ngModelController, and check the link above to see how you should interact with that to achieve a two way binding like behaviour. (using $viewValue, $render and $setViewValue)

Cannot get textarea value in angularjs

Here is my plnkr: http://plnkr.co/edit/n8cRXwIpHJw3jUpL8PX5?p=preview You have to click on a li element and the form will appear. Enter a random string and hit 'add notice'. Instead of the textarea text you will get undefined.
Markup:
<ul>
<li ng-repeat="ticket in tickets" ng-click="select(ticket)">
{{ ticket.text }}
</li>
</ul>
<div ui-if="selectedTicket != null">
<form ng-submit="createNotice(selectedTicket)">
<textarea ng-model="noticeText"></textarea>
<button type="submit">add notice</button>
</form>
</div>
JS part:
$scope.createNotice = function(ticket){
alert($scope.noticeText);
}
returns 'undefined'. I noticed that this does not work when using ui-if of angular-ui. Any ideas why this does not work? How to fix it?
Your problem lies in the ui-if part. Angular-ui creates a new scope for anything within that directive so in order to access the parent scope, you must do something like this:
<textarea ng-model="$parent.noticeText"></textarea>
Instead of
<textarea ng-model="noticeText"></textarea>
This issue happened to me while not using the ng-if directive on elements surrounding the textarea element. While the solution of Mathew is correct, the reason seems to be another. Searching for that issue points to this post, so I decided to share this.
If you look at the AngularJS documentation here https://docs.angularjs.org/api/ng/directive/textarea , you can see that Angular adds its own directive called <textarea> that "overrides" the default HTML textarea element. This is the new scope that causes the whole mess.
If you have a variable like
$scope.myText = 'Dummy text';
in your controller and bind that to the textarea element like this
<textarea ng-model="myText"></textarea>
AngularJS will look for that variable in the scope of the directive. It is not there and thus he walks down to $parent. The variable is present there and the text is inserted into the textarea. When changing the text in the textarea, Angular does NOT change the parent's variable. Instead it creates a new variable in the directive's scope and thus the original variable is not updated. If you bind the textarea to the parent's variable, as suggested by Mathew, Angular will always bind to the correct variable and the issue is gone.
<textarea ng-model="$parent.myText"></textarea>
Hope this will clear things up for other people coming to this question and and think "WTF, I am not using ng-if or any other directive in my case!" like I did when I first landed here ;)
Update: Use controller-as syntax
Wanted to add this long before but didn't find time to do it. This is the modern style of building controllers and should be used instead of the $parent stuff above. Read on to find out how and why.
Since AngularJS 1.2 there is the ability to reference the controller object directly instead of using the $scope object. This may be achieved by using this syntax in HTML markup:
<div ng-controller="MyController as myc"> [...] </div>
Popular routing modules (i.e. UI Router) provide similar properties for their states. For UI Router you use the following in your state definition:
[...]
controller: "MyController",
controllerAs: "myc",
[...]
This helps us to circumvent the problem with nested or incorrectly addressed scopes. The above example would be constructed this way. First the JavaScript part. Straight forward, you simple do not use the $scope reference to set your text, just use this to attach the property directly to the controller object.
angular.module('myApp').controller('MyController', function () {
this.myText = 'Dummy text';
});
The markup for the textarea with controller-as syntax would look like this:
<textarea ng-model="myc.myText"></textarea>
This is the most efficient way to do things like this today, because it solves the problem with nested scopes making us count how many layers deep we are at a certain point. Using multiple nested directives inside elements with an ng-controller directive could have lead to something like this when using the old way of referencing scopes. And no one really wants to do that all day!
<textarea ng-model="$parent.$parent.$parent.$parent.myText"></textarea>
Bind the textarea to a scope variable's property rather than directly to a scope variable:
controller:
$scope.notice = {text: ""}
template:
<textarea ng-model="notice.text"></textarea>
It is, indeed, ui-if that creates the problem. Angular if directives destroy and recreate portions of the dom tree based on the expression. This is was creates the new scope and not the textarea directive as marandus suggested.
Here's a post on the differences between ngIf and ngShow that describes this well—what is the difference between ng-if and ng-show/ng-hide.

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.

AngularJS binding json to a form input

I can easily bind data to a div or pre tag with the code:
<div id="json_route{{route.id}}" ng-bind="items.route{{route.id}} | json"></div>
However, I want to try and bind this data to a hidden form input, I tried:
<input type="hidden" name="json_route{{ route.id }}"
ng-model="items.route{{route.id}} | json" />
Which returns me an error of:
Error: Non-assignable model expression: items.route2 | json (<input type="hidden" name="json_route2" ng-model="items.route2 | json">)
So obviously I cannot use | json when using ng-model. The angular docs are still a bit sparse and I can't seem to find how to assign this correctly, even if I can? Thanks :)
I need to get this json data loaded into my pyramid application, and assigning it into a hidden form field seemed the best way todo it, or should I be doing this in a different way?
"To be able to render the model into the view, the model has to be able to be referenced from the scope." (src: Angular Guide).
Angular needs to be able to reference the value in your ngModel expression to a $scope variable in your controller.
With ng-bind it worked, because ng-bind is not the same as ng-model. ngBind simply takes your expression and evaluates it inside the current scope and than replaces the text of the host element with the result. As Guide tells us, the value of ng-model must be an assignable angular expression to data-bind to.
To have your hidden input contain the json string representation of your 'items.route2' model you could setup a $watch expression in your controller which would "prepare" the json string of your model whenever it changes. See plnkr example.
Try using ng-init:
<input type="hidden" name="..." ng-model="items.route{{route.id}}"
ng-init="items.route{{route.id}} = data_from_server_here">
See also https://stackoverflow.com/a/12657601/215945.

Resources