Angular Custom Validation of Nested Attributes - javascript

I have an Angular-based form for which I want to apply specific validation:
<form name="createProject" novalidate>
<span ng-show="createProject.projectName.$invalid">Please enter a name for the project.</span></br>
<input type="text" ng-model="newProject.project.name" name="projectName" placeholder="Project Name" required>
<div ng-repeat="topic in newProject.project.topics_attributes">
<span>Topic {{$index + 1}}</span>
<input type="text" ng-model="topic.name" placeholder="Topic Name">
<input type="text" ng-model="topic.feed_size">
<div ng-repeat="topic_source in topic.topic_sources_attributes">
<span>Topic {{$parent.$index + 1}} Source {{$index + 1}}</span>
<select ng-model="topic_source.platform" ng-options="platform for platform in platforms" topic-source-required parent="$parent.topic.name">
<option value="">-- platform --</option>
</select>
<input type="text" ng-model="topic_source.keywords" placeholder="Topic Keywords" topic-source-required parent="$parent.topic.name">
<button ng-show="$last" ng-click="addTopicSource($parent.topic)">Add Topic Source</button>
</div>
</div>
<button ng-click="addTopic()">New Topic</button>
<input type="submit" ng-click="addProject()" ng-class="{ disabled: createProject.$invalid }" ng-disabled="createProject.$invalid" value="Add Project">
</form>
Back
In the form, a project can have either zero or multiple topics. Each topic must have at least one topic source. Basically, I want to create validation that checks if a topic is being added (added being defined as having a name) and if so require that it has at least one topic_source with platform and keyword fields entered associated with it.
Is this even possible? I'm not very familiar with Angular custom validation so I'm not sure how to begin implementing something like this.
**Edit**
Current progress on custom validation directive:
angular.module('dashboard').directive 'topicSourceRequired', ($parse) ->
require: "ngModel"
link: (scope, elem, attrs, ctrl) ->
topicName = $parse attrs.parent
scope.$watch topicName, (topicName) ->
ctrl.$parsers.unshift checkForExistence if topicName
checkForExistence = (topicSource) ->
if topicSource
ctrl.$setValidity "topicSourceRequired", true
else
ctrl.$setValidity "topicSourceRequired", false
topicSource
Ok, getting closer for this validation. Right now the validation seems to be checking if the two topic_source fields have values independent of whether or not the parent's name field has a value, AND the topic_source fields only register as invalid if a field has been entered and then erased.
How do I make sure the checkForExistence function is only called when topicName has a non empty string value? Second, how do I make sure blank fields are invalid without having to modify them somehow?

You need to create a custom directive that requires ngModel which gives you access to the ngModelController (look at the require option when creating a directive).
Basically, you can listen for changes to the value by registering with the $viewChangeListeners, read the current value from $viewValue, and set the validation through the controller function
$setValidity(validationErrorKey, isValid);
The validationErrorKey you set is the a boolean value on the $erros object of the controller.
Take a look at this page in the docs to get you going:
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

Related

Angular 2+ multi-part form validation, how to check validity of single input

I have a form, and the form has multiple inputs that are all bound to different variables. Before submitting the form, I need to do validity checks, pristine checks, etc. For example, I want my submit button to be disabled if every part of the form is pristine, or if something is invalid.
Using Angular 5, I am trying to get access to the .pristine, .valid, and .invalid flags for each input field, but the values are either undefined or "cannot get .pristine of undefined".
I am able to get these flags on the entire form itself, but this doesn't help, because I want to know how to get it for each individual input.
Here is my current code (I've removed a number of my inputs to simplify the example).
<form #editDetailsForm="ngForm" name="editDetailsForm" >
<label for="name"> Name </label>
<input type="text" id="name" name="name" maxlength="40" [(ngModel)]="myName" required />
<label for="description"> Description </label>
<textarea id="description" name="description" maxlength="250" [(ngModel)]="myDescription" required ></textarea>
<button id="submit" type="button"
[disabled]="saveButtonDisabled(editDetailsForm.invalid, editDetailsForm.name.invalid, editDetailsForm.description.invalid)"
(click)="updateDetails()" >
Save
</button>
</form>
If you see, I bind disabled attribute on the Save button to saveButtonDisabled() function, where I want to pass in information about each input's validity. The first argument, editDetailsForm.invalid returns a true or false, but the other values return undefined.
How do I check validity of these individual inputs?
EDIT: I realize I can derive all of this info inside my component because all of the input values are bound. However, it'd be easier just to check a flag or two.
I'm not sure I totally understand what you want to do, but this is how you get access to the form controls .pristine, .invlaid
<input type="text" id="name" name="name" #name="ngModel" maxlength="40" [(ngModel)]="myName" required />
The #name="ngModel" sets a template reference to the FormControl angular creates
Then you should be able to do something like this:
<input type="text" id="name" name="name" #name="ngModel" maxlength="40" [(ngModel)]="myName" required />
<div *ngIf="name.pristine">
Name is Pristine
</div>
Just to clarify, the individual form fields bubble up to the form itself. So if any field has been touched, then the whole form will be pristine == false.
You can access the input controls using the .controls property, like:
<button id="submit" type="button"
[disabled]="editDetailsForm.controls.name?.invalid || editDetailsForm.controls.description?.invalid">
Created a stackblitz. https://stackblitz.com/edit/angular-5ir4k7
Added template reference variable for ngModel and validate using isValid.

angular 2: validate a input field with built in validators

Angular 2 has some built in validators like maxlength, minlength and pattern. i have written this line of code:
<input id="mobile" type="text" class="form-control" name="mobile"
minlength="10">
If a user enters for example a 5-digit number and goes to the next input field an error should appear that says like 'The number you have typed is incorrect'.
How to do that with Angular 2?
In angular 1 is very easy like this
<p class="help-block" ng-if="editForm.mobile.$error.minlength">
<h3>This field is required to be at least 10 characters.</h3>
</p>
If you're using template driven forms then you need to change your input to have an ngModel binding to a variable in your component, and a template variable name:
<input id="mobile" type="text" class="form-control" name="mobile" [(ngModel)]="mobileNumber"
#mobile="ngModel" minlength="10">
<div *ngIf="mobile.errors?.minlength">Minimum length not met</div>
That link in the comment on your question is very good reading for understanding this, as it has instructions such as
We set the name attribute of the input box to "mobile" so Angular can
track this input element and associate it with an Angular form control
called name in its internal control model.
We use the [(ngModel)] directive to two-way data bind the input box to
the hero.name property.
We set a template variable (#mobile) to the value "ngModel" (always
ngModel). This gives us a reference to the Angular NgModel directive
associated with this control that we can use in the template to check
for control states such as valid and dirty.
Here's a plunker you can muck about with: http://plnkr.co/edit/dxtIkZI1dhp07lvega5P

Dynamically set form name in ng-messages

Within multiple forms in my app, I am trying to use the same directive which is an email input field complete with validation (it sources its value from a list of recently used emails but that is not pertinent to this question). Because the form in which the directive is used is different depending on where I use it, the ng-messages attribute needs to be dynamically rendered.
I have managed to get ng-messages to dynamically set itself, but it is not working as expected. Here is the code:
form.html
<form name="joinForm">
<rt-recents-menu ng-model="vm.roomName"/>
</form>
directive.coffee
recents = angular.module 'rtRecents', ['ionic', 'ngMessages']
.factory('rtRecentsService', require './recentsService')
.directive('rtRecentsMenu', (rtRecentsService, $ionicActionSheet) ->
restrict: 'E'
template: require './recentsMenu.html'
scope:
ngModel: '=?'
require: ['?ngModel', '^form']
transclude: false
replace: false
link: (scope, element, attrs, ctrls) ->
ngModel = ctrls[0]
formCtrl = ctrls[1]
scope.formName = formCtrl.$name
module.exports = recents.name
directive.html
<div class="email-line">
<input type="email"
name="roomName"
required
ng-model="ngModel"
ng-keydown="onKeyDown()"/>
<div id="email-error-messages" ng-messages="{{formName}}.roomName.$error" multiple role="alert">
<div ng-message="required" class="warn" translate>VALIDATION.EMAIL_REQUIRED_ERROR</div>
<div ng-message="email" class="warn" translate>VALIDATION.EMAIL_INVALID_ERROR</div>
</div>
</div>
In my case, I have a that displays each of the returned rows from a db query. And each row had its own form within this repeat loop.
I ran into two problems:
The ngMessage errors didn't display correctly because they didn't seem to be tied to the right fields/form.
I couldn't disable the "save" button per-form. The $invalid didn't seem to work.
Here's what works (trimmed decorators, etc so you only see the essential points):
<div ng-repeat="row in rows | orderBy: 'sortBy'"
ng-init="formName = 'siteEdit' + row.id">
<form name="{{formName}}" ng-submit="view.startSave(row.id)">
<!-- I have "control" buttons at the top of my form to save, cancel, etc. here are the two related to saving -->
<span class="glyphicon glyphicon-save pull-right q-palette-color-2" ng-disabled="view.isPageBusy()"
ng-show="! this[formName].$invalid" ng-click="view.startSave(row.id)" title="Save"/>
<span class="glyphicon glyphicon-ban-circle pull-right q-palette-accent-2"
ng-show="this[formName].$invalid" style="padding-left:10px;cursor: pointer; cursor: hand;" data-toggle="tooltip" title="Correct Errors To Save"/>
<label for="siteName{{row.id}}"> Name:</label>
<input id="siteName{{row.id}}"
class="form-control input"
type="text"
placeholder="name"
ng-disabled="view.isPageBusy()"
ng-model="row.name"
name="name"
required />
<div ng-messages="formName.name.$error">
<div ng-message="required">This field is required</div>
</div>
.....
</form>
</div>
Key points:
ng-init in the ng-repeat builds the formName variable.
form validation for save/etc uses this[formName].$invalid
Field validation is referenced in the format of formName..$error
Something to note:
I couldn't get the ng-disable to actually disable the "button" based on it being invalid or not (the ng-click executed no matter what, maybe because I didn't use <button> but used <span> w/ glyphicon). So I opted to have one displayed when you can save, the other displayed when you can't. Works better this way since the person can visually see they can't save, and if they hover over they'll see they need to correct the form first.

Angular validators and ng-maxlength use

I've got the following div, which I want to add the bootstrap's class "has-error" if the input length is over 50 characters. This is the HTML:
<div class="form-group" ng-class="{has-error:[formData.titulo.$error]}">
<label for="inputTitulo">Título</label>
<input type="titulo" class="form-control" id="inputTitulo"
maxlength="50" ng-maxlength="50" ng-model="formData.titulo">
</div>
How can I make this work? I guess when you reach 50 characters, ng-maxlength throws a error, like the $error object, but I have no clue on what object is, how to access it, and if I have to do some more work in the controller or directive.
Any help here? I can't find any "easy" info regarding this issue and Angular validators.
edit 1:
I've seen all your responses, learned something new thanks to you, but this is still somehow not working. It currently is this way:
<div class="form-group" ng-class="{'has-error': formData.titulo.$error.maxlength}">
<label for="inputTitulo">Título</label>
<input type="titulo" class="form-control" id="inputTitulo" maxlength="50" ng-maxlength="50" ng-model="formData.titulo">
</div>
Also tested checking the length directly, as one of you suggested. But none of these solutions seem to work: it never adds the has-error class. Why?
To have the errors published on the scope, a form directive is required.
<div ng-form="form1" ng-class="{'has-error': form1.text1.$error.maxlength}">
<input name="text1" ng-model="formData.foo" ng-maxlength="50">
</div>
(Notice that the above uses the name attribute of the input to publish the form data - really, the ngModelController - on the scope)
So, the above works, and it's preferable if you do form validation. But, if you just need to check the length of some input, you don't have to use form validation - you could just check the model directly:
<div ng-class="{'has-error': formData.foo.length > 50}>
<input ng-model="formData.foo">
</div>
as you are using ng-model to make validations ,, this class ng-invalid will be added to your input
docs : https://docs.angularjs.org/api/ng/directive/ngModel
to use $error you need to access it using forms and names not ng-model ,, and the ng-class should be bound to the $error.maxlength not $error only
tutorial : https://scotch.io/tutorials/angularjs-form-validation
If you use the maxlength, a user will never be able to enter more characters than that, so you will never get the ng-maxlength error. It doesn't make sense to use maxlength and ngMaxlength together IMHO.
See the example on docs.angularjs.org/api/ng/directive/ngMaxlength (open the example in plunker and add maxlength attribute)

Issue with directives

I'm learning the use of the directives in AngularJS. I have a password.html file with a input password and then I have created my own directive 'passwordRequirement' to force the user to meet these requirements when creating a user.
This is the code of password.html:
<div class="form-group">
<label for="user_password">Password</label>
<input type="password" data-ng-change="passwordChanged();" data-ng-model="user.password" class="form-control" id="user_password" name="user_password"
placeholder="Password" required>
<div data-ng-show="enteredPassword">
<h4>For your security, the password needs to meet the following requirements</h4>
<password-requirement binding="passwordContainLowercase">Password must contain at least a lowercase character</password-requirement>
<br>
<password-requirement binding="passwordContainUpperCase">Password must contain at least uppercase character</password-requirement>
<br>
<password-requirement binding="passwordLengthValid">Password length must be between 12 and 20 characters</password-requirement>
<br>
<password-requirement binding="passwordContainDigit">Password must contain at least a digit</password-requirement>
</div>
</div>
As each requirement is binded to a different property in the scope, in the definition of the directive I have specified in the scope the variable "binding".
See the code of the definition of the directive:
app.directive('passwordRequirement',function()
{
return {
restrict: 'E',
scope:{binding:'#'},
templateUrl:'partials/content/common/passwordRequirement.html',
transclude:true
};
});
And then the html template for the password requirement (which consists in a label plus a checkbox to be checked or not depending if the requirement is met or not):
<div style="display:inline-block;">
<span data-ng-transclude></span>
<input type="checkbox" data-ng-disabled="true" data-ng-model="binding" data-ng-checked="binding">
{{binding}}
</div>
As you can see I want bind the ng-model to the variable I have specified in password.html. However if I print the value of the bind property using {{binding}}, it prints the correct values. But if I inspect the source code of the page I see:
<input type="checkbox" data-ng-disabled="true" data-ng-model="binding" data-ng-checked="binding" class="ng-pristine ng-valid" disabled="disabled" checked="checked">
Which means that in the page the variable binding it is not being interpreted so that it is not working properly. Why? I cannot do data-ng-model={{binding}} , so what is the solution for this?
Is this doable in AngularJs? If so, what is the right way to achieve that? I though it was the way I'm doing but apparently is not.
Thank you very much in advance.
Try to use two-way binding '=' instead of text binding '#' in your directive:
app.directive('passwordRequirement',function()
{
return {
restrict: 'E',
scope:{binding:'='},// here is the change
templateUrl:'partials/content/common/passwordRequirement.html',
transclude:true
};
});

Categories

Resources