Non-unique input names in Angular scope - angularjs

I can refer to properties of inputs in an Angular scope like this:
<form name>.<input name>.$dirty
However this doesn't work if I have multiple inputs with the same name (e.g. in a sub-section of the form generated with ng-repeat). In that case <form name>.<input name> just holds a reference to the first input with that name.
I'm trying to DRY the logic around displaying error messages / classes. To do that I really need to be able to check validity, dirty state and so on. The only other way I can think to do it is to look for ng-(dirty|invalid) classes on the input element which feels like a dirty hack.
I also tried using the $index variable of ng-repeat in the input name (e.g. <input name="foo[{{$index}}]">) but then there's only a single property in the FormController with that literal string – not separate ones like foo[0], foo[1] etc.
Is there another way to handle this?

Related

access angularjs form field that contains a number

I have a list of form fields that are generated as the result of an ng-repeat . As such I use the {{$index}} of the repeat loop to name the fields, i.e. ` which leads to:
<input name="myInput0">
<input name="myInput1">
<input name="myInput2">
...
etc. Now I'm trying to access the fields' $valid attribute from the form in the standard angularjs way i.e. myForm.myInput{{$index}}.$valid which resolves to e.g. myForm.myInput0.$valid which I understand won't work because it's accessing a variable and numbers won't be allowed.
However I then tried to access it with myForm['myInput{{$index}}'].$valid, e.g. myForm['myInput0'].$valid which I thought might work but still doesn't. Is there any way possible to access the form field when it contains a numeral? (or other illegal char like a hyphen)?
e: I'm using angularjs 1.2 which might explain why this isn't working. Does anyone know of a workaround for pre 1.3 angular?
The correct expression will look like myForm['myInput' + $index].$valid
I can think of two ways of workaround.
First option:
Put a ng-change on each input and validates it there manually.
Second option:
Retrieve all input elements, interate over each and validates manually
You can get them with this
var inputs = $document[0].querySelectorAll('#rankingForm input');

Using the same directive more than once in the same form

I'm having trouble with a task that seems trivial to me, but I just haven't got it right yet. I have a form and in that form I use a home-cooked directive called time-range. This directive has two input fields for start-date and end-date. Later on, in the view where the form is defined I want to validate each field using code similar to this:
<li ng-show="form.createAssignment.fromDateField.$error.dateinput">{{::$parent.lang.from_date | capitalize}} {{::$parent.lang.has_to_be_on_format}} {{::$parent.lang.yyyy_mm_dd}}</li>
Well, this works just fine, BUT only for validating the input field in the last directive in the form. If I enter something wrong in the first directive, the form is invalid (and thus the error list is shown) but the text that specifies what is wrong does not show up, presumably because the input in the last directive is correct.
So, somehow I would like to be able to qualify which directive I refer to, maybe something like:
<li ng-show="form.createAssignment.directive1.fromDateField.$error.dateinput">{{::$parent.lang.from_date | capitalize}} {{::$parent.lang.has_to_be_on_format}} {{::$parent.lang.yyyy_mm_dd}}</li>
However, I haven't yet succeeded with this. Does anyone have a suggestion how this can be done?
ng-form uses input name for validation, so it's hard to target specific input if you are using ng-repeat or repeated directives.
One option would be using ng-form directive, which means creating an inner form inside the form. There are plenty of guides on how to do this.
Another option would be passing a unique name (prefix/suffix) via your directive parameter. But unfortunately the variable name won't work automatically, you'll have to compile the template again after the name is passed in.

Seemingly unable to access content generated in the link() function while attempting to unit test an angular directive

I am working on an enterprise project, and as a part of that project we have a directive for creating form fields with appropriate document markup and validation/error handling. As an example, consider something like the following:
<form name="myForm">
<my-form-field label="My Field">
<input type="text" name="myField" id="myField" ng-model="myField" ng-required="true" />
</my-form-field>
</form>
The directive works exactly as expected, properly surrounding my input field with the common/expected markup, adding appropriate event handlers, etc.
One key piece of functionality is a convenience feature where the directive "notices" that the child form field has an attribute value for ng-required of true (either directly, as in this example, or because it is bound to something which is evaluated). This attribute does the "normal" angular thing (hooking into field-level validation in the angular workflow), but then my directive also notices it's presence in the child element, and appends " *" to the label value when taking the label attribute of the directive element and creating an HTML as part of the markup which is generated.
When attempting to unit test this in jasmine/karma, I see the logic in the template() portion of my directive firing (e.g., the tag is generated), but not the items in the link() portion (e.g., adding the asterisk to the label text, updating the for="" attribute of the label tag to match the id="" attribute of the child tag).
After spending the morning researching various demos/tutorials on unit testing angular directives, I found an example which didn't help me much...but did offer a smaller/more basic example of what I am seeing: http://plnkr.co/edit/tirhLwFEXLKSzukbsW1q?p=preview
In this example, seeing the attribute values set by the link() function of the direction is easily handled (see the expectations at lines 23-25), but the rendered html still doesn't seem to be compiled (outputting element.html() anywhere in that it() still shows the angular variables - like {{values.center}} - instead of the compiled values). I recognize that I could put computed values on the scope and then follow this pattern of validating by checking the directive's scope...but in my case, I am further modifying the html within the link() portion (quite successfully actually, despite what the unit tests appear to be showing).
What am I missing in terms of being able to "see"/validate the compiled HTML (e.g., validate that an asterisk was added to the label text when appropriate)? I thought about doing all of my markup work in the template() portion of my directive, but then I can't see how I could inspect the child input element (to get the id for the for="" attribute of the label, and to check if it is required or not so that I know if the asterisk is appropriate for the label text).
EDIT:
- This plunkr is most advanced of my three scenarios, and fails (basically a copy of my code, including trying to combine all the angular template generation/compilation into one common function, based on an example I had seen in a few different places): http://plnkr.co/edit/AxGAUKYFvm2yf1vPsWD3?p=preview
In the third plunkr (see my comment below for the first two, each of which works as expected), the error thrown is actually "TypeError: currentSpec.queue is undefined...", which is likely my culprit - but I can't seem to find a way around it. I actually wasn't seeing this error locally...because I had seen some advice in a few places to change this method within angular-mocks.js:
isSpecRunning = function() {
//return currentSpec && (window.mocha || currentSpec.queue.running);
return !!currentSpec;
};
The second line is the original, which was commented out/replaced with the third line. This removes the error shown in the Plunk, and instead my unit test runs just fine...except for it doesn't seem to show the final compiled version (e.g. the label's for attribute is never populated, and the asterisk is not added to the label text when appropriate). So with the suggested change, my test runs ok - but fails to see the
"final" markup which I see when I actually run my site. Without the change to angular-mock.js, I see the error you see in the plunkr.

Angularjs : creating dynamic form

I'm developing a friend invitation feature for a website.
Only requirements are : by email and has a max number of invitations at a time.
My idea is the following :
At the start, user only sees one email field. When he enters an email adress in the only field, angularjs validates it (email format check) and creates an additional email field.
Now, I come from a jquery background and I think it's bad practice to manipulate DOM with angular.
How would one do it with angularjs ?
Is it a good idea to create a factory that "produces" (from a template file) fields ?
Can a library like bootstrap ui help me write simpler code for form validation and error management
This Plunker might fulfill your need at its closest: http://plnkr.co/edit/5qRXQ1XGzUnhYjLCiyYR?p=preview
The key point in this technique is letting the user directly edit a dynamic list of models. Indeed in the example, $scope.invites contains your values. The trick here is referring to them as models:
<input type="email" class="invite" name="invite{{ $index }}" ng-model="invites[$index].mail" ng-change="checkInvite($index)" />
$index being the index of the current ng-repeat iteration. checkInvite function will take care of watching changes in your invites fields.
Notes:
invites is an array of objects, this way we're sure not to mess with ng-repeat, iterating over the reference that we handle (vs models that would be handled by angular)
The field's name is useful to manually check the field's validity: in the controller we can check a field's validity accessing $scope.formName.fieldName.$valid
I also added an extra test that checks if the user clears a non-last filled-in field. In this case, we remove the field.
Have fun using angular!
Personally, I would find the design confusing, since I wouldn't know I could have more email addresses. At the minimum, I would want a + to indicate to the user that s/he can add more addresses. Think of how airlines do "multiple destinations" searches on their Websites.
However, if you are set at this, use an array in the scope. I am using a table for this, but anything will do.
<input ng-model="newemailaddress"></input><button ng-click="addEmail">Add</button>
<table>
<tr ng-repeat="addr in addresses"><td>{{addr}}</td></tr>
</table>
And your controller something like:
.controller('MyCtrl',function($scope) {
$scope.addresses = [];;
$scope.newemailaddress = "";
$scope.addEmail = function() {
// do validation
if (valid) {
$scope.addresses.push($scope.newemailaddress);
$scope.newemailaddress = "";
};
};
})

How should I integrate my directive and scope object in the view?

In my controller I have an array of objects. The object is called Well has a few properties, one of which is Location, which stores a string like "A1", "B4", "B13", etc. The location indicates a position on a grid. The letter represents the row, and the number represents the column.
Now that I have this nice list of objects, I would like to display them all on a grid in my view. When I say grid, I mean that loosely. The grid I have come up with is a series of divs, each with an id equal to a location name.
I have created a directive called tile that will display the properties of a single object. The directive looks like so:
<div class="row">
<div class="col-md-3" ng-repeat="well in wellArray">
<ul><li ng-repeat="prop in well">{{ prop }}</li></ul>
</div>
</div>
Great! And then I can create a tile in my view for a specific Well in the list of Well objects like so:
<div tile name="{{my.getName()}}" dil="{{my.getDilution()}}"></div>
If this list of objects was ordered by the location property, I could simply turn it into an array of arrays, one array for each row, and then use a double ng-repeat in my view. Unfortunately they are not in order, and I do not want to create a sorting method given the format that the location property is in. If i were to do the double ng-repeat on this list as it is now, I would end up with a grid of tiles that are in no particular order.
Given my limited exposure to javascript, I thought of using jquery's .append() method.(note: i have referenced jquery before angular, so angular.element() will use the jquery library instead of jqlite so I can use jquery selectors) In my view I created a bunch of divs in the following format:
<div id="A1"></div>
<div id="A2"></div>
etc.
And then in my controller I created a method that attempts to append a single Well which has a location of "A1" to the element on the view that has an id="A1". My code looks like so:
angular.element('#A1').append('<div tile name="{{my.getName()}}" dil ="{{my.getDilution()}}"></div>');
I thought it would append the div with the tile directive, to the div with id="A1", however, it does nothing. In fact, there are no errors at all.
Surely my psuedo jquery approach is not the best way to go about this. Not only is it not working (no idea why, maybe because angular needs to compile something somehow), but it's also not a very Angular approach. I keep reading in tutorials and introductions to "not use jquery at all for the first few weeks" and "90% of the things you'll waste lines of code in jquery, can be done suceinctly in Angular". Someone please lend this poor excuse of a programmer a hand!!
Just following your example in comments with .append, instead of iterating over your array and appending elements to a container element, create a conceptual representation of the data, and then use it in the view.
In controller, do something like the following:
$scope.wellData = {};
for (well in wellArray){
var key = wellArray[well].getLocation();
$scope.wellData[key] = well;
}
Then in the view, do ng-repeat over wellData:
<div id="item.getLocation()" ng-repeat="item in wellData">
<div tile name="{{item .getName()}}" dil="{{item .getDilution()}}"></div>
</div>
You definitely should stay away from jQuery in controllers. Just assume that there is no DOM in controllers whenever you get the urge to do anything related to DOM. Controller deals with ViewModels which are conceptual representation of the view, but it is view-independent. Whenever you break that separation, you make your controllers harder to test, and you make your view more difficult to change. And, by going against MVVM principles, you will keep bumping into issues with AngularJS.

Resources