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.
Related
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.
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;
});
I'm trying to implement a multilingual text input field with a little dropdown button to the left for selecting the language. For instance, when the dropdown menu shows 'de' the text field should be bound to model.multilingualData['de'].someField and so on.
My first approach was to set ngModel to model.multilingualData[selectedLanguage].someField. But when the user selects a different language without filling in the field correctly, no error is set on the form, because the model now points to a different object.
My next idea was to create an entire element directive without ngModel, but then I wouldn't be able to do other validations like ngMaxLength.
I couldn't find anything helpful on the web either. Any idea on how to implement this properly?
EDIT
Here's a little fiddle that illustrates the problem: http://jsfiddle.net/FZ2kg/
Not only that the form appears valid when you switch languages, the previous field value is also deleted, because the model is set to null when the field becomes invalid.
would be nice if you use this awesome external directive for multilanguage!
https://angular-translate.github.io/
I hope it helps
If you need to have form validation for all language variations and you're loading all languages at once in your model, can't you just create an input per language in the form and hide all but the currently selected language?
Here's a fiddle: http://jsfiddle.net/jvl70/w3rstmwd/5/
<form name="myForm">
<div ng-repeat="(lang, value) in model.multilingualData"
ng-show="lang==stuff.currentLanguage">
<ng-form name="innerForm">
<div ng-class="{ 'has-error': innerForm.anything.$invalid }">
<input type="text" name="anything" ng-model="value.someField" ng-maxlength="6"/>
</div>
</ng-form>
</div>
</form>
(At first I tried to use dynamic names for each input but found that the individual field $invalid wasn't available for dynamically named inputs. See this post to get around it: Dynamic validation and name in a form with AngularJS.
As an alternative to ng-form, you could use dynamic names for each input and try one of the custom directives on the link.)
I guess if there were many possible languages, this approach might get slower but it's ok for a few languages.
What is the difference between required and ng-required (form validation)?
AngularJS form elements look for the required attribute to perform validation functions. ng-required allows you to set the required attribute depending on a boolean test (for instance, only require field B - say, a student number - if the field A has a certain value - if you selected "student" as a choice)
As an example, <input required> and <input ng-required="true"> are essentially the same thing
If you are wondering why this is this way, (and not just make <input required="true"> or <input required="false">), it is due to the limitations of HTML - the required attribute has no associated value - its mere presence means (as per HTML standards) that the element is required - so angular needs a way to set/unset required value (required="false" would be invalid HTML)
I would like to make a addon for tiago's answer:
Suppose you're hiding element using ng-show and adding a required attribute on the same:
<div ng-show="false">
<input required name="something" ng-model="name"/>
</div>
will throw an error something like :
An invalid form control with name='' is not focusable
This is because you just cannot impose required validation on hidden elements. Using ng-required makes it easier to conditionally apply required validation which is just awesome!!
The HTML attribute required="required" is a statement telling the browser that this field is required in order for the form to be valid. (required="required" is the XHTML form, just using required is equivalent)
The Angular attribute ng-required="yourCondition" means 'isRequired(yourCondition)' and sets the HTML attribute dynamically for you depending on your condition.
Also note that the HTML version is confusing, it is not possible to write something conditional like required="true" or required="false", only the presence of the attribute matters (present means true) ! This is where Angular helps you out with ng-required.
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...