AngularJS ng-required better implement from controller? - angularjs

I'm thinking of a good way to implement ng-required.
Let's say I have a bunch of inputs with ng-required in my app.
<input type="text" id="one" />
<input type="date" id="two" />
<input type="radio" id="three" />
<input type="checkbox" id="four" />
I would like to do something in a controller, where I could pass an array of required fields. I'm thinking that if I made an array of elements such as:
var myEl = angular.element( document.querySelector( '#some-id' ) );
and some how set the required property that way.
I write a directive which would decide from an array if the field is required, if it does not exist in the array, it's not required if it does, it's required.
Ultimately, I would like to have an array that allows passing of fields in such a way:
var reqArray = ('#id', ('#id1' || 'id2')) etc.
Works the same as conditional logic operators:
#id is required
#id1 || #id2 is required, but not both.
Not sure where to begin, or if it's feasible in Angular.
This would make it easier to modify required fields in large applications without having to modify the actual html.

It can be done, but angular already provides its own ways to validate forms. Some brief details:
The form tag must have a novalidate attribute in order to prevent HTML5 validation. <form name="myForm" novalidate>
With that, now angular can take charge of the validation by adding a "required" attribute to your inputs <input ng-model="myModel" type="text" required>
By this point, angular has taken control of your validation, it can also validate other HTML5 attributes like pattern. <input pattern="[0-9][A-Z]{3}" type="text" title="Single digit followed by three uppercase letters."/>
I suggest you take look at this official guide (also take a look at the directives guide on that same site, I wanted to link it but I don't yet have the rep).
That being said, what you are trying to accomplish is also possible, but rather redundant since you would have to add an attribute to your inputs anyway (the directive, and angular is able to validate already) and also require ngModel in the directive.
I made this plunkr to show you how to do it, but take notice of the extra work needed in order to validate something that angular already does natively, I'd leave this kind of work for expanding on validations, like comparing items or models.
Also, querying a selector directly like in your suggestion is not considered "the angular way". A better way would be to add the directive to your form element and access the element through the 'element' parameter in the directive.
Hope this helps.

Related

AngularJS Conditional attributes from data

I am creating a AngularJS app to render forms dynamically from a generic for definition data structure. So the form details (including all validation rules, data types etc) are defined in a database and a single AngularController should "draw" the form.
For background - here is the rough structure of the defiintion:
Form
Sections
Fields
It works fine - but I have a problem when adding attributes to the <input> element on the form based on the definition. So for example, lets say field1requires a min and max length validation, but field 2 does not. This is achieved by adding the ng-minlength and ng-maxlength attributes to the input element for field1 and NOT adding these to the input element for field2.
Some googling sugested that the recently removed ng-attr directive might have been a sollution - but since its removed, I am stuck?
<ng-form name="frmTSFApp">
<uib-tabset>
<uib-tab ng-repeat="aSection in tsf3fd.section track by aSection.ukey"
index="aSection.display_order"
heading="{{aSection.display_label}}">
<ng-form name="frmSection{{aSection.display_order}}">
<div ng-repeat="aField in aSection.fields track by aField.ukey"
class="form-group"
ng-class="getFormClass(frmSection{{aSection.display_order}}[aField.data_name],aField)">
<label class="control-label">{{aField.field_label}}</label>
<input ng-required="{{aField.require_kind == 'R'}}"
type="text"
class="form-control"
ng-model="tsf3fd.model[aField.data_name]"
name="{{aField.data_name}}" />
</ng-form>
</uib-tab>
</uib-tabset>
</ng-form>
The above snippet is what we use to render the form - so the ng-required attribute works nicely, since its always there and its value can be resolved from the expression. For each field we "know" all the validations we need to add (ng-minlength or ng-pattern etc etc) but not sure how to do this?
In psuedo code I would like something like this "inside: the input element:
if (aField.validations.minmax)
{
ng-minlength = "{aField.validations.minmax.min}"
ng-maxlength = "{aField.validations.minmax.max}"
}
Hope my explanation makes sense.
I am starting to doubt my approach - it seems a simpler approach is to render the "literal" form markup externally through something like XSLT from the deffinition data. We have done this and it works fine, but I thought working directly from the data would be so "cool" and would simplify the process.
Anyway - any help would be appreciated.
You can just use:
<input
ng-required="{{aField.require_kind == 'R'}}"
ng-minlength="{{aField.validations.minmax ? aField.validations.minmax.min : '' }}"
ng-maxlength="{{aField.validations.minmax ? aField.validations.minmax.max : '' }}"
type="text"
class="form-control"
ng-model="tsf3fd.model[aField.data_name]"
name="{{aField.data_name}}" />
Setting ng-minlength or ng-maxlength to empty string has the same effect as not setting them.

bind email containing apostrophes to angular model

I have an input type email that I validate against my own custom regular expression. It in turn is bound to an angular model something like:
js
$scope.user = {};
html
<input type="email" ng-model="user.email" />
I would like for the email to allow apostrophes but the email doesn't bind to the model unless it passes the built in html5 validation. I'd like to override or switch off this validation since I have my own custom regex in place.
I've tried adding the novalidate tag to the form wrapper and also adding a pattern to the input but not getting anywhere. Please see jsfiddle:
http://jsfiddle.net/HB7LU/19499/
Any ideas greatly appreciated
C
EDIT: The reason I'm not using type="text" is because I want the email keyboard set to be there when accessing from mobile.
Angular do not support the apostrophe(') in email Id , if need to valid the apostrophe in angular, need to change in angular file regular expr
(/^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
to
/^[A-Za-z0-9._%+'-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/.
It will Work perfectly.
If you already have a regex for validation, you could set the type of the input tag to "text" so that then it would not do any validation and then the regex you have already created could be used to do the validation you want. Also, this will allow you to change the validation method if you ever decide to change how you want validation to work.
Here's the tag:
<input type="text" ng-model="user.email" />

AngularJS 1.3 pattern validation binding not working

I have been using Regex pattern validation with AngularJS for the last several versions and it has worked fine.
My application requires that validation patterns are exposed by a scope property, to which the corresponding AngularJS validation directive is bound. Prior to v1.3, it looked something like this:
// On the controller
$scope.validationPattern = "^\\d*$"; // Allow only numeric digits
<!-- in the HTML page --->
<input type="text" name="age" ng-pattern="/{{validationPattern}}/" />
Having now updated AngularJS to v1.4 (bypassing v1.3), I find that the above approach no longer works. Looking at the migration notes for v1.3, I see that this is expected behavior and that a new approach is required, which looks something like this:
// On the controller
$scope.validationRegexp = /^\d*$/; // Use a RegExp instead of a string
<!-- in the HTML page --->
<input type="text" name="age" pattern="{{validationRegexp}}" />
However, I simply can't get this to work. If I place the validation pattern inline (within the HTML input element) it works fine, but when moved onto the scope object and bound to the pattern or ng-pattern directive, no validation occurs.
Here's a JSFiddle that demonstrates the problem.
Any suggestions please?
You should use only the name of the scope variable:
<input type="text" name="age" ng-pattern="validationPattern" />

How to use angularjs form validation in a non-angular SPA app

I wonder if anyone can help. On the surface of it, my question title may sound kind of stupid! I'm trying to use angularjs form validation in a non-angularjs application! Let me try to explain ...
We have a 'traditional' web app. Its not an SPA. The backend is java, and the java app server manages the session and the data within. Each page is a full http request/response. (There are a small number of ajax request/responses, but these are to add some bling to the page, rather than it's core functionality). In this respect, the architecture of the app is very traditional/old-skool, in that the server-side java code is responsible for generating the markup and populating form field values from it's version of the model data held in it's session store. (I think this is the crux of the problem)
The app is predominantly a HTML form based application, and to enhance the UX we have written some javascript field validators based around jQuery. For a number of reasons these have started to get a bit out of control, and we are exploring alternative options.
A simple google search finds countless jQuery plug ins for form validation. We are looking at these, but random jQuery plug ins are not our favoured approach (we tend to steer clear of 'somebloke.com' plug ins because we can't guarantee how well they're written, browser compatibility, future maintenance, how well they work with other plug ins etc - we've had our fingers burnt with this kind of thing before)
So we are looking at other approaches, and are currently exploring the use of angularjs.
Being able to 'gently introduce' angularjs into our architecture has some advantages. It's from a stable best-of-breed organisation (ie. it's not 'somebloke.com') so is well supported and maintained. It encourages us to write our js in a very modular & testable manner (our current rats nest of jQuery plugins, callbacks etc is far from that!). angularjs form validation is based around html5 standards and is declarative and semantic. It gives us a way forward for migrating our other jQuery based code to something better (angularjs directives). And overall, if we can layer angularjs into our current app, it gives us a good foundation for converting the app to a modern SPA at some point in the future.
Rewriting the entire app as an angularjs (or any other mv* framework) SPA at this point in time is not an option, so as mentioned above, we are looking at introducing small bits of functionality at a time; and today's challenge is form validation.
So, that's the background.
I've stripped out our current js client side validation, and our server-side java code is generating markup like this:
<form method="POST" action="/renew">
<input name="firstname" type="text" value="alf" />
<input name="surname" type="text" value="garnet" />
<input name="age" type="number" value="88" />
<input type="submit" />
</form>
(where the values for the input fields have been populated server-side from the model held by the server)
I've added the angularjs library to the page, and have got form validation working as follows:
<form method="POST" action="/renew" novalidate name="renewForm"
ng-controller="yourDetails" ng-submit="submitForm(renewForm, $event)">
<input name="firstname" type="text" value="alf" required ng-model="firstname"/>
<input name="surname" type="text" value="garnet" required ng-model="surname"/>
<input name="age" type="number" value="88" required ng-model="age"/>
<input type="submit" />
</form>
app.controller('yourDetails', function($scope) {
$scope.submitForm = function(form, $event) {
if (!form.$valid) {
$event.preventDefault();
return false;
}
};
});
This is a reasonable starting point. Broadly speaking it works in that angularjs is handling the form validation and submission. The submitForm method is executed, and if the form is not valid then the if block is entered and the form submission is cancelled. From here I can see it would be easy to add in the field error messages etc using ng-show etc.
The problem however is the use of ng-model on each html field. As I understand it I need to use this so that angularjs binds the field to the form, and can therefore track each fields valid status.
However, ng-model also appears to setup the 2-way data binding and sets the value of the field to it's version of the model data ... which is empty. For example:
Our server-side template might contain this:
<input th:field="*{firstname}" type="text" required ng-model="firstname"/>
Which might generate this markup:
<input name="firstname" value="alf" type="text" required ng-model="firstname"/>
The markup that gets served to the client includes value="alf"
But then angularjs steps in and sets up 2-way binding for the field. Because we don't have a firstname property in the angularjs scope, it initialises one with a blank value, and sets that blank value in the DOM of the field.
This results in the page being rendered by the browser with blank values in the fields, even though server-side we have values in the model, and the server has correctly generated the markup etc.
So, I think I understand the core problem and why it's happening. My question is, can I do angularjs form validation without the ng-model attribute on each field, or is there a version of the ng-model directive that only does 1-way binding - specifically DOM -> model
Any help would be very much appreciated;
Thanks
Nathan
When generating your form at server side, you can initialize your model with ng-init:
<input ng-init="firstname='alf'" th:field="*{firstname}" type="text" required ng-model="firstname" />
OK, #Alexandre's answer was almost right, and it was his answer that pointed me in the direction of my final solution (so he should get the credit for this really :) )
ng-init does work as #Alexandre has suggested. The reason I couldn't get it to work was that I was trying to use it on a number field.
The following works because the value being set on the model with ngInit and the html input type are both text/string:
<input ng-init="firstname='alf'" th:field="*{firstname}" type="text" required ng-model="firstname" />
I was trying it on the age field as follows:
<input ng-init="age='88'" th:field="*{age}" type="number" required ng-model="age" />
This does not work because the age property was being set on the model as a string, but the html input type is a number. The following does work:
<input ng-init="age=88" th:field="*{age}" type="number" required ng-model="age" />
This led me to realise that the value being set on the angular model needs to match the data type of the html input type (certainly true of chrome, not sure about other browsers) (ie. string properties in the model - even if they are parse-able as numbers - cannot be used in a html number field with ngModel)
With this in mind, I decided there were 2 options. I could either do it server-side in the code that generates the markup:
<input ng-init="age=(some-potentially-complex-logic-to-workout-whether-its-a-string-or-number)" th:field="*{age}" type="number" required ng-model="age" />
Or I could do it client-side with a custom directive. In the end I went with a custom directive because a) it meant I could have a go at writing a directive (all part of the learning :)) and b) I realised there might be other cases that need special consideration which might make doing it server-side even more complex (ie. select fields don't have a value, they have a selected index of which you need to get it's value; radio buttons all have a value but you only want to set the value of the checked radio)
Here's what I came up with:
angularApp.directive('lvInitializeValueOnScope', function() {
return {
restrict: 'A',
link: function(scope, element, attrs, controller) {
var propertyName = attrs.name,
propertyValue = attrs.value,
elementName = element.get(0).tagName.toLowerCase(),
fieldType = ( elementName === 'input' ? attrs.type.toLowerCase() : elementName ),
// set expression to assume propertyValue is a string value
expression = propertyName + '=\'' + ( !!propertyValue ? propertyValue : '' ) + '\'';
// if the input field type is number and propertyValue is parse-able as a number
if (fieldType === 'number' && !isNaN(parseFloat(propertyValue))) {
// set expression without quotes surrounding propertyValue
expression = propertyName + '=' + propertyValue;
}
// if the field is a html select element
if (fieldType === 'select') {
// propertyValue will be blank because select elements don't have a value attribute
// instead, we need to use the value of the child option element that is selected
propertyValue = $(element.html()).filter(":selected").val();
// set expression to assume propertyValue is a string value
expression = propertyName + '=\'' + ( !!propertyValue ? propertyValue : '' ) + '\'';
}
// if the input field type is a radio button but its not checked (selected)
if (fieldType === 'radio' && !element.is(':checked')) {
// we need to reset the expression so a blank value is used
// doing this means that only the checked/selected radio button values get set on the model
expression = propertyName + '=\'\'';
}
// evaluate the expression, just as angular's ngInit does
scope.$eval(expression);
}
};
});
(it looks more complex than it actual is because I've left the comments in - strip those out and there's really nothing to it)
To use it, each field that I am using ng-model on, I also need to use the attribute data-lv-initialize-value-on-scope. You don't need to pass any value on the attribute, the directive gets everything it needs from the element and attr parameters
So far it caters with text, number, radio and select fields. The pages I'm working on don't have any checkboxes, but as and when I come across those I dare say I'll need to add some code for those.
It works, and I think I prefer this to using ng-init with a load of server-side to determine whether to set a value, what its data type should be, etc
The only downside I can see at the moment is that processing a radio button set might be inefficient. IE. if you have 10 radio buttons, each marked up with ng-model and data-lv-initialize-value-on-scope; all 10 will run the data-lv-initialize-value-on-scope directive and set a value on the scope. At least 9 of the iterations will set a blank value on the scope, and at most only 1 will set the actual value on the scope.
Hey-ho, it seems to work for now :). Hope this helps someone in the future
Nathan

One form element updating multiple pieces of the model?

I am looking at using angular for a project at work but I have a question first. We have a single page application that's pretty intricate. We do have a basic model set up but some fields in the model are redundant. If I couldn't reduce the redundancy, what steps could I take in angular so that one form element changes two variables in the model?
I've put together a bare bones jsfiddle of what I'm hoping to do:
<div ng-app>
<input type="text" ng-model="yourName" placeholder="Enter a name here" /><br/>
<span>Hello {{yourName}}!</span><br/>
<span>Hello {{altName}}!</span>
</div>
How could I change this around so that the input would assign it's value to both yourName as well as altName? I've tried what I thought would be obvious such as comma or pipe delimiting the ng-model attribute to no avail. Is it possible?
You could set a $watch on the yourname-Variable within your controller and then change the altName in its callback. Should look like this:
$scope.$watch('yourName', function(){
$scope.altName = $scope.newName;
});

Resources