Form Validation error while using angularjs - angularjs

I'm trying to add a simple validation for my input text box using angularjs and trying to show an inline error message. I have tried a lot, but still I'm not able to get the $valid, $ error, $invalid etc. These attributes are getting as undefined. I have given the reference to angularjs.js file. All other functionalities are working, but don't know what's happening. I'm new to angularjs, hope someone will help me in this regard. My code snippet is given below.
<sp-modal-dialog show='modalShown'>
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
EDIT:
I'm using a directive to show the popup form. Please see the code below.
//Directive to link the modalDialog as Element
contextualHelpModule.directive('spModalDialog', function () {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true,
transclude: true,
link: function (scope, element, attrs) {
scope.hideModal = function () {
scope.show = false;
};
},
template: '<form id="popupForm" name="popupForm" novalidate><div ng-show="show"><div class="popup-main" ng-show="show"><div class="popup-container"><div class="popup-content" ng-transclude><div ng-click="hideModal()" class="popup-close">X</div></div></div></div><div class="popup-overlay"></div></div></form>'
};

you need to wrap that into a form:
<form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</form>
I hope that it helps.

Yes, as jvrdelafuente explains, you will need to wrap it in a
<form name="popupForm">
tag. Once you do that, then you will have access to the properties via popupForm.txtTitle.$error.required, etc. Otherwise, they are not defined as you have observed. Unfortunately, I have recently learned that if you are placing your form tag in an aspx page using a SharePoint master page, the master page places its own form tag without a name attribute. ASP.NET will then strip your form out because it does not allow a form within a form. I am still searching for the rest of the story.
Update....
To be able to get past the issue with ASP.NET stripping out the AngularJS form tag, use an ng-form tag instead:
<ng-form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</form>

Use ng-form at place of form, it will work fine, as see code in below
<ng-form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</ng-form>

Related

Udating parent object from isolated scope in directive is not working at all

I have a directive defined like that:
myApp.directive('someDirective', function() {
return {
restrict: 'A',
scope: {
disableButton: '=',
},
templateUrl: 'sometemplate.html',
controller: 'SomeDirectiveController'
}
});
The controller for the directive looks as follows:
mLoan.controller('GiversDirectiveController', function (
$scope,
) {
$scope.checkGiversAmount = function () {
var giversCurrentTotalValue = $scope.stocksTotalAmount - $scope.giversCurrentAmount;
}
this is the directive view
<div class="row">
<div class="col-lg-2">
<label for="customer">Wybierz klienta</label>
<select id="customer"
name="customer"
class="form-control"
ng-model="selectedCustomerId"
ng-options="customer.Id as customer.Name for customer in customers"
ng-change="getGiversFunds(selectedCustomerId)"
required=""></select>
</div>
<div class="col-lg-2" ng-show="customerFunds.length > 0">
<label for="customerFunds">Wybierz fundusz</label>
<select id="customerFunds"
name="customerFunds"
class="form-control"
ng-model="selectedFundId"
ng-options="customerFund.Id as customerFund.Name for customerFund in customerFunds"
ng-blur="updateGiverFund(selectedCustomerId, selectedFundId)"
ng-required="customerFunds.length > 0"></select>
</div>
<div class="col-lg-2">
<label for="giversCurrentAmount">Ilość</label>
<input type="number" min="1"
max="{{giversCurrentAmount}}"
id="giversCurrentAmount"
name="giversCurrentAmount"
class="form-control"
ng-model="giversCurrentAmount"
ng-change="checkGiversAmount(selectedFundId)"
ng-blur="updateGiverStockAmount(selectedCustomerId, giversCurrentAmount)"
required />
</div>
<div class="col-lg-2">
<label for="commission">FEE%</label>
<input type="number"
min="1"
id="commission"
name="commission"
class="form-control"
ng-model="commission"
ng-blur="updateGiverCommission(selectedCustomerId, commission)"
required />
</div>
<div class="col-lg-4">
<label for="minimalCommission">FEE Min</label>
<input type="number"
min="1"
id="minimalCommission"
name="minimalCommission"
class="form-control"
ng-model="minimalCommission"
ng-blur="updateGiverMinimalCommission(selectedCustomerId, minimalCommission)"
required />
</div>
</div>
And a parent view where I'm Using the directive with the button:
<div ng-repeat="giver in givers">
<div givers-directive givers="givers"
givers-current-amount="giversCurrentAmount"
disable-giver-side-add="parent"
stocks-total-amount="stocksTotalAmount">
</div>
</div>
<div class="row">
<div class="col-lg-12 margin-top-10px">
<button id="addGiverSide"
name="saveLoan"
class="btn btn-primary"
ng-click="addGiverSide(AddLoansForm)"
ng-disabled="disableButton">
Dodaj
</button>
</div>
</div>
Now my problem and question ath the same time is how I can update the disableButton from the parent model. Before you say this is a duplicate I've ran through the solutions taht was on stackoverflow mainly this one: How to access parent scope from within a custom directive *with own scope* in AngularJS? and that one: What are the nuances of scope prototypal / prototypical inheritance in AngularJS? but nothing seems to help. What is straneg (or not) I can't use the $parent to get to that. I have to go through: $scope.$parent.$parent path and then it's getting updated. Besides that it looks ugly and strange, it's not working when I'm trying to updated this variable from the parent view. To sum up one effect is working and the other is not. And after changing is the other way around. I've tried to wrap up the disable button into an object and try to do the change but this does not work also. Or maybe I'm doing it wrong. Please kindly help me because I'm running out of ideas. What am I doing wrong?
UPDATE
I'm adding the rest of the directive view to show the concept what I'm trying to achieve here. THere are drop downs wich I want them to be independent. Because without the directive with separated (or isolated) scope the two dropdowns are getting filled by changing the other. I want to change them indepenently. I've managed to achieve thet through out the isolated scope. But now I can't seem to get the disabling button working.
By defining a scope inside your directive, you lock your directive out of the parent's scope. If you want to make use of a parent scope, you should set your scope to true.
You can read more on the matter here: http://www.sitepoint.com/practical-guide-angularjs-directives/
EDIT: Example
myApp.directive('someDirective', function() {
return {
restrict: 'A',
scope: true,
templateUrl: 'sometemplate.html',
controller: 'SomeDirectiveController'
}

angular JS dynamic tooltip creation

EDIT:
It seems like the Error or wrong handling is because the scope does not gets updated when the email field is not valid... is there a way to chang that?
this is my first question on stackoverflow so i hope i will do it right.
I am pretty new to angular js and i am creating some basics at the moment.
in my demo app i created a normal form in a bootstrap style and i was planing to create a directive to show the errors in a bootstrap way. so far so good. that was working and my next step was to create a angular js bootstrap tooltip directive when the form is not valid. the thing is, that i wanna do this dynamic.
i post some code to explain it better:
<b>HTML:</b>
<div class="container" ng-controller="LoginCtrl as vm">
<form id="login" name="vm.loginForm" class="form-signin" ng-submit="vm.login()" novalidate>
<div show-errors>
<input type="email" name="username" ng-model="vm.credentials.username" class="form-control" placeholder="Email address" required autofocus>
</div>
<div show-errors>
<input type="password" name="password" ng-model="vm.credentials.password" class="form-control" placeholder="Password" required>
</div>
<button class="btn btn-lg btn-primary btn-block" id="submit" type="submit">
Login
<span class="spinner"><i class="fa fa-refresh fa-spin"></i></span>
</button>
</form>
</div>
<b>showError Directive:</b>
(function () {
angular.module('testapp.Validate', []).directive('showErrors', validationDirective);
/**
* #name validate directive
*/
function validationDirective($compile) {
return {
require: '^form',
restrict: 'A',
link: function (scope, el, attrs, formCtrl) {
var inputNgEl = angular.element(el[0].querySelector("[name]"));
var inputName = inputNgEl.attr('name');
inputNgEl.bind('blur', function () {
toogle(inputNgEl, el, formCtrl[inputName]);
});
scope.$on('show-errors-check', function () {
toogle(inputNgEl, el, formCtrl[inputName]);
});
}
}
}
function toogle(inputNgEl, fromGroup, inputField) {
fromGroup.toggleClass('has-feedback has-error', inputField.$invalid);
if (inputField.$invalid && !fromGroup[0].querySelector(".glyphicon-remove")) {
inputNgEl.after('<span class="glyphicon form-control-feedback glyphicon-remove"></span>');
} else if (!inputField.$invalid) {
if (fromGroup[0].querySelector(".glyphicon-remove") != null) {
fromGroup[0].querySelector(".glyphicon-remove").remove();
}
}
}
})();
That is working so far . it just adds a has-feedback,has-error class to the parent div and a span with an error-icon after the input.
but back to my plan, now I also want to create a dynamic tooltip for the input. so I planed to add something like that in the "toogle" function
inputNgEl.attr('uib-tooltip',"aaaa");
inputNgEl.attr('tooltip-placement',"right");
inputNgEl.attr('tooltip-trigger',"mouseenter");
inputNgEl.attr('tooltip-class',"testTool");
But that is not working because the input field got already compiled before.
so I asked google about it and there I found some solutions with $compile
$compile(inputNgEl)(scope);
But when I am using it, and I type in a valid email address the field gets reset. aaa#aaa (still working) but after I add the aaa#aaa. (the field gets reseted - I guess compiled again). the tooltip would work btw.
Can anybody help me with that or is there a better solution to create a dynamic angular bootstrap tooltip?
Maybe you need to add a $watch on the input element, and see if it changes, add a tooltip in the input element and compile it

AngularJS form validation: indicate required fields to user

I would like my form labels to display a red asterisk next to the label when the corresponding form control has a required attribute.
Instead of hard coding the asterisk, I desire a way to append the asterisk to the label dynamically during page load if the label's corresponding input, select or textarea is required (the element the label corresponds to).
I created the directive below, and the directive works. But is there a better, more native way to accomplish my goal? This directive finds all the div.form-group containers and adds a red * character after the label if the corresponding form control inside the div.form-group has a required attribute.
myApp.directive('labelsRequired',function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs){
elem.find('div.form-group').each(function(i, formGroup){
var formControls = $(formGroup).find('input, select, textarea');
console.log(formControls)
if (0 !== formControls.length && undefined !== $(formControls[0]).attr('required')){
jLabel = $(formGroup).find('label');
jLabel.html(jLabel.html()+ "<span class='red-color'>*</span>");
}
})
}
}
});
The directive assumes all inputs, selects, and textareas are inside a div.form-group container.
<div class='form-group'>
<label>First Name</label><!-- this label gets an asterisk -->
<input name='fname' required />
</div>
<div class='form-group'>
<label>Favorite Food</label><!-- this label does not get an asterisk -->
<input name='favFood' />
</div>
You don't need a directive, there are built-in form properties you can use with filters like ng-show, look:
<div ng-app="myApp" ng-controller="myCtrl">
<form name="userForm" novalidate>
<div class='form-group'>
<label>First Name</label>
<input name='fname' ng-model="fname" required />
<label ng-show="userForm.fname.$dirty && userForm.fname.$error.required">* Required field</label>
</div>
<button type="submit">Submit</button>
</form>
</div>
If you define an ng-model for the input you can deal with it looking if it is filled or not. You can also check it only after the user "dirty" it with userForm.fname.$dirty, so the label will be shown only after a user try to input something but then clear it. Try playing with it here JSFiddle
Building off of Corey's answer:
I just used compile rather than link, as I saw that my required attribute was not being applied to my input elements. I also included a select tag for any dropdowns that I had.
app.directive('inputRequired', function () {
return {
restrict: 'A',
compile: function (elem) {
elem.find('label').append("<sup><i class='fa fa-asterisk'></i></sup>");
elem.find('input').attr('required', 'required');
elem.find('select').attr('required', 'required');
}
};
});
If you're not using the built-in Angular validation, you could restructure your directive and attach it to your .form-group element. Like this:
app.directive('inputRequired', function() {
return {
restrict: 'A',
link: function(scope, elem, attr) {
elem.find('label').append('<span>*</span>');
elem.find('input').attr('required', 'required');
}
};
});
Your HTML would then look like:
<div class="form-group" input-required>
<label>Name</label>
<input name="name" />
</div>
<div class="form-group">
<label>Food</label>
<input name="food" />
</div>
However, if you haven't looked into the built-in validation with Angular, I would recommend using it.
This might come too late and it might not be too elegant but it works, if anyone needs it:
<label ng-show="userForm.fname.$validators.hasOwnProperty('required')">* Required field</label>

Angular directive attributes aren't being passed through

I'm trying to shorten my form code by making directives for each element, however my directive is displaying none of what I'm passing to it and the model isn't being bound.
HTML:
<formstring dataBinding="project.title" dataTitle="Title" dataPlaceholder="title" />
directive:
app.directive('formstring', function () {
return {
restrict: 'AEC',
dataBinding: '=',
dataTitle: '#dataTitle',
dataPlaceholder: '#dataPlaceholder',
dataHelp: '#dataHelp',
templateUrl: '/app/js/directives/form/string.html',
};
});
string.html:
<div class="form-group"> 2 <label for="{{dataTitle}}" class="col-sm-2 control-label">{{dataTitle}}</label >
<div class="col-sm-10">
<input type="text" class="form-control" id="{{dataTitle}}" placeholder="{{da taPlaceholder}}" ng-model="dataBinding">
<p ng-show="dataHelp" class="help-block">{{dataHelp}}</p>
</div>
</div>
project is a $scope object that has an attribute 'title'.
What am I missing? Why does this show up at a blank input with none of the attributes filled in and why does the binding not work?
You haven't understood how directives are configured correctly. I suggest you read the documentation, it may help you understand better.
In the meantime, here is what your HTML, directive code and template should look like (there is also a working demonstration on Plunkr):
HTML:
<formstring data-binding="project.title"
data-title="Title Demo"
data-placeholder="title placeholder"
data-help="My help text">
</formstring>
directive:
app.directive('formstring', function () {
return {
restrict: 'E',
scope: {
binding: '=',
title: '#',
placeholder: '#',
help: '#'
},
templateUrl: '/app/js/directives/form/string.html',
};
});
Template (string.html):
<div class="form-group">
<label for="{{title}}" class="col-sm-2 control-label">
{{title}}
</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="{{title}}" placeholder="{{placeholder}}" ng-model="binding">
<p ng-show="help" class="help-block">{{help}}</p>
</div>
</div>
You need to change how you're creating isolate scope:
app.directive('formstring', function () {
return {
restrict: 'AEC',
scope: {
dataBinding: '=',
dataTitle: '#dataTitle',
dataPlaceholder: '#dataPlaceholder',
dataHelp: '#dataHelp'
},
templateUrl: '/app/js/directives/form/string.html',
};
});
Read the doc for details about what an isolate/isolated scope means because it has an effect on the overall scope.
edit:
I didn't notice this additional issue before. Your camel case scope properties become snake cased when you use your directive (see Mobin's answer):
<formstring data-binding="project.title" data-title="Title" data-placeholder="title" />
In your template, however, the properties are still camel cased as you have:
<div class="form-group"> 2 <label for="{{dataTitle}}" class="col-sm-2 control-label">{{dataTitle}}</label >
<div class="col-sm-10">
<input type="text" class="form-control" id="{{dataTitle}}" placeholder="{{da taPlaceholder}}" ng-model="dataBinding">
<p ng-show="dataHelp" class="help-block">{{dataHelp}}</p>
</div>
</div>
This is because the bindings in your template are JSON properties, whereas the attributes when you use your directive's properties are XML properties.
There are minor tweaks I'd apply to your template, for instance id="{{dataTitle}}" can easily break HTML standards that require the id attribute is unique... you probably want to use name="{{dataTitle}}" instead. name could still cause issues, but it wont' break document.getElementById for example.
Also, I'd use ng-bind whenever possible:
<p ng-show="dataHelp" class="help-block" ng-bind="dataHelp"></p>
It should be
<formstring data-binding="project.title" data-title="Title" data-placeholder="title" />
.

AngularJS: Fields added dynamically are not registered on FormController

I have the following static form in AngularJS:
<form name="myForm" class="form-horizontal">
<label>First Name:</label>
<input type="text" name="first_name" ng-model="entity.first_name">
<label>Last Name:</label>
<input type="text" name="last_name" ng-model="entity.last_name">
</form>
Angular creates a FormController for me and publishes it into the scope (under the form name). Which means I have access to properties like the following:
$scope.myForm.first_name.$error
$scope.myForm.last_name.$invalid
...
This is super useful!
But in my case I'm building a form dynamically, using directives:
<form name="myForm" class="form-horizontal">
<field which="first_name"></field>
<field which="last_name"></field>
</form>
The <field> directives don't resolve to actual <input> elements until after a while (after I've fetched some data from the server, linked the directives, etc.).
The problem here is that no field properties are defined on the form controller, as if dynamic fields didn't register with the FormController:
// The following properties are UNDEFINED (but $scope.myForm exists)
$scope.myForm.first_name
$scope.myForm.last_name
Any idea why? Any solution/workaround?
You can see the entire code in this jsFiddle:
http://jsfiddle.net/vincedo/3wcYV/
Update 7/31/2015 This has been fixed since 1.3, see here: https://github.com/angular/angular.js/issues/1404#issuecomment-125805732
Original Answer
This is unfortunately a short coming of AngularJS at the moment. Angular's form validation doesn't work with dynamically named fields. You can add the following at the bottom of your HTML to see exactly what's going on:
<pre>{{myForm|json}}</pre>
As you can see, Angular isn't getting the dynamic input name right. There's currently a work around involving nested forms that can get kind of nasty, but it does work and (with a little extra work) will submit the parent form without trouble.
If you want, you can go drum up more support for the issue: GitHub Issue - dynamic element validation. Either way, here's the code:
http://jsfiddle.net/langdonx/6H8Xx/2/
HTML:
<div data-ng-app>
<div data-ng-controller="MyController">
<form id="my_form" name="my_form" action="/echo/jsonp/" method="get">
<div data-ng-repeat="field in form.data.fields">
<ng-form name="form">
<label for="{{ field.name }}">{{ field.label }}:</label>
<input type="text" id="{{ field.name }}" name="{{field.name}}" data-ng-model="field.data" required>
<div class="validation_error" data-ng-show="form['\{\{field.name\}\}'].$error.required">Can't be empty.</div>
</ng-form>
</div>
<input type="submit" />
</form>
</div>
</div>
JavaScript:
MyController.$inject = ["$scope"];
function MyController($scope) {
$scope.form = {};
$scope.form.data = {};
$scope.form.data.fields = []
var f1 = {
"name": "input_1",
"label": "My Label 1",
"data": ""
};
var f2 = {
"name": "input_2",
"label": "My Label 2",
"data": ""
};
$scope.form.data.fields.push(f1);
$scope.form.data.fields.push(f2);
}
I ran into a similar problem myself and what i did to work around it was to place the name of the field before calling $compile on the template. A simple string.replace did the trick. Then again that was only possible because i was getting the field templates in through http and had access to the template text.
update: here is a fiddle with a little hack to make your example work
app.directive('field', function($compile) {
var linker= function(scope, element){
var template = '<input type="text" name="{{fname}}" ng-model="model">'
.replace('{{fname}}', scope.fname);
element.html(template)
$compile(element.contents())(scope)
}
return {
restrict: 'E',
scope: {
fname: '=',
model: '='
},
replace: true,
link: linker
};
});
http://jsfiddle.net/2Ljgfsg9/4/

Resources