I have a description of field in my AngularJS code:
input.form-control(type='password' placeholder='password' id='passwordInput'
name='passwordInput' ng-model='credentials.password
ng-enter='loginByEnter(credentials,loginForm.$valid)'
ng-maxlength='{{passwordMaxLen}}' required form-control)
The line in question is ng-enter one. The function loginByEnter is:
$scope.loginByEnter = function(credentials,htmlValidated) {
if (htmlValidated) {
$scope.login(credentials);
}
};
And custom directive ng-enter is
.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
The purpose of all this: I want user to proceed with login when he netered password and hit Enter (so he does not have to press separate Login button elsewhere). But I also need to validate the form. Can it be done more elegantly, without querying a validation function on hitting enter?
You need to take a look at this article.
It explains how you can do form validation in AngularJs.
Simply put, you need to enclose the input tag inside a form tag and then you need to use the ng-submit directive on the form tag to allow the user to submit the form without hitting the login button.
You don't need to make use of your custom ng-enter directive.
"ngEnter" or similar keyboard event processors are not needed for form submission. One can use ngSubmit on <form> elements to handle either key press (Enter) or button click submission.
View
<form ng-submit="onLoginSubmit()">
<input type="text" ng-model="username" />
<input type="password" ng-model="password" />
<input type="submit" />
</form>
Controller
function Controller($scope, ...) {
$scope.onLoginSubmit = function() {
if(validate($scope.username, $scope.password)) {
// Magic
}
}
}
Related
In this plunk I have a form with an input field and a button. The input field's ng-blur and the form submission call the same function validate(). Problem is that when the cursor is on the input field AND the user clicks on "Submit" the function is triggered twice, as there's a blur when the focus passes to the button. In that scenario, I need the function to be called once.
If you look at the console.log you'll see how the function is triggered twice. Any ideas how to fix this?
HTML
<form name="myForm" ng-submit="validate(2)">
<input type="text" ng-model="someField" ng-blur="validate(1)" />
<br/>
<button type="submit">Submit</button>
</form>
Javascript
var app = angular.module('example', []);
app.controller('ctl', function ($scope) {
$scope.validate = function(x) {
console.log("field validated - " + x);
};
});
Here's I would like to solve this. I put a submitted variable which determines whether form is submitted or not. So, in the validate function, we first determine whether submitted is true or not. But, the important point here is we would need to make it false when we complete the API call. Something like this:
app.controller('ctl', function ($scope) {
$scope.submitted = false;
$scope.validate = function(x) {
if($scope.submitted === true) {
return;
}
$scope.submitted = true;
console.log("field validated - " + x);
// change $scope.submitted back to false in success and error callback of
// the API call
};
});
This solution has another side benefit. It stops users from submitting the form twice (using multiple clicks on submit button?)
We could have utilized formName.$submitted too but here one of the function calls will have it and another wouldn't.
Here's updated plunker
that's obvious, but you can always discriminate on the basis of event type, just pass an extra attribute denoting the type of event
EDIT : ng-change instead of ng-blur (description in comment)
html
<form name="myForm" ng-submit="validate(2)">
<input type="text" ng-model="someField" ng-change="validate(1)" />
<br/>
<button type="submit">Submit</button>
</form>
js
$scope.validate = function(x) {
console.log("field validated - " + x);
};
If anyone can guide me on how to detect if the user fills the form with the autofill option of google chrome.
I have a directive where each time the user fills in the field and change the blur sends an event to google analytics.
But also I need to detect whether the user has filled the form with the autofill option of chrome and push the data for each of the fields to google analytics.
Part of my directive:
element.bind('blur', function (e) {
if ((e.target.value !== 0) && typeof value !== 'undefined') {
if (_.has(ga_data, 'sendEvent')) {
analyticsService.sendEvent(ga_data.sendEvent);
}
if (_.has(ga_data, 'action') && ga_data.action === 'blur') {
analyticsService.sendEvent(ga_data);
}
}
});
You can use two way data binding here and watch the ng model on the form field for changes
<form method="post">
First name:<input type="text" name="fname" ng-model="user.fname"/><br />
Last name: <input type="text" name="lname" ng-model="user.lname"/><br />
E-mail: <input type="text" name="email" ng-model="user.email"/><br />
Phone: <input type="text" name="phone" ng-model="user.phone"/><br />
Address: <input type="text" name="address" ng-model="user.address"/><br />
</form>
Then inside your angular controller you can do something of this sort.
angular.module('app', []).controller('AppController', function($scope){
$scope.user = { };
$scope.$watch('user', function(nv, ov){
console.log(nv);
}, true);
});
There might be some cases that you need to handle though, to prevent sending multiple requests because $watch function will be triggered every time the value in the text field changes.
Here is a fiddle which triggers $watch when any value in a form field changes, be it via autofill or manual entry by user.
In this case, the way to detect when Chrome auto-fills a form instead of the user doing it is by detecting the absence of an event having occurred, such as the keyup event. Consider the block of HTML and Javascript below. I have the text input field marked with a data attribute that is initially set to false. If the user fills out anything in the form by typing, then the attribute is set to true. The moment when you record whether or not the user filled out the form is on form submit. Then you can check the fields of the form and see if the user entered the input his or herself.
<form onsubmit="dosomething()">
<label for="somefield"></label>
<input type="text" id="somefield" onkeyup="this.setAttribute('data-entered', (this.value != '').toString());" data-entered="false" />
<input type="submit" value="submit"/>
</form>
The reason why you need to use a keyboard event and send the information when the form is submitted is because you can only tell if auto-fill took place when any of the fields have values even when the user typed nothing in. This part is less about code, and is more about what needs to be done so to take a proper measurement.
Based on input-directive src, angular sets up a listener for cases of change, input, paste etc.
And whenever the browser autofills the input element, this listener is called and viewValue is commited through array of $parsers as of here ngModel directive src.
So eventually you can avoid additional scope.$watch and just rely on $parsers to send ga track event just on linking phase of each input element with directive. And btw don't forget to destroy you parser func right after first usage (i.e. browser autofill), so it will not spam further on viewValue change.
Here's an example:
angular
.module('app', [])
.directive('detectPrefill', function() {
return {
require: 'ngModel',
link: {
pre: function(scope, element, attrs, ctrl) {
function detectPrefill (viewValue) {
//send GA data
//...
// just checking that detectPrefill func is destroyed after first usage
viewValue && console.log(viewValue);
ctrl.$parsers.splice(
ctrl.$parsers.indexOf(detectPrefill),
1
);
return viewValue;
}
ctrl.$parsers.push(detectPrefill);
}
}
};
});
Hope this helps.
I’m quite new to Angular but I love it already!
I need to write a couple of reusable components for a wizard.
I would like to handle the submit event from the form in the directive as well, is this possible?
<form ng-submit="submit()" name="exampleForm">
<foo data="someData"></foo> <!-- I need to handle the submit event in directive as well-->
<input type="submit" id="submit" value="Submit"/>
</form>
If the user presses enter or clicks the button on the form the directive has to make a call to the backend and double check the data.
If the check is successful the form will be valid.
I built a simple example here
Thanks in advance!
Stefan
Yes it's possible.
The first issue is targeting the correct $scope. Right now your code targets the submit() function on the controller. The <form> can't see the $scope of the directive due to how the html elements are nested.
Since you want to submit() from the directive, then the directive template should also include the <form> element and the submit input button.
Invoking foo will look like this:
<foo data="someData" name="exampleForm"></foo>
If you would rather keep foo the way it is (and that's a totally legit way to go about it) then you'll need a new directive with the submit() function. So you'd have 2 directives working together (Very angular! Many wow!).
What you will want here is a custom directive for form validation. Custom form validators are added to the $validators object on the ngModelController.
For a simple example for an "integer" directive (found in the Angular docs here under "Custom Validation"), the directive would be defined like this:
var INTEGER_REGEXP = /^\-?\d+$/;
app.directive('integer', function() { return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.integer = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
if (INTEGER_REGEXP.test(viewValue)) {
// it is valid
return true;
}
// it is invalid
return false;
};
} }; });
And it would be used in the form like this:
<form name="form" class="css-form" novalidate>
<input type="number" ng-model="size" name="size" min="0" max="10" integer />{{size}}<br />
</form>
There is also an option for asynchronous validation, adding the validator to the $asyncValidatorsobject. More info is found at the link mentioned above. The working example using both is found here
Here's a working example:
http://plnkr.co/edit/sckCOq3a50PBjat2uixk?p=preview
I've created another 2-way binding on the directive's scope (called "submit"), so you can specify the name of the submit function as it will be seen from ExampleController's scope (I used "directiveSubmit" here).
scope: {
data: '=',
submit: '='
},
.
<foo data="someData" submit="directiveSubmit"></foo>
The directive's controller then creates that function and assigns it to its own scope, and that also assigns it to ExampleController's scope via the magic of 2-way binding.
Next you can run that submit function from ExampleController's scope and it will actually be referring to the newly created function in the directive.
<form ng-submit="directiveSubmit(exampleForm)" name="exampleForm" novalidate>
Notice that I'm also passing the exampleForm into that function. Without that the directive will have a hard time getting access to it.
Finally, I've also added novalidate to the form because if you don't do that some browsers (i.e. Chrome) will handle form validation in a way that may (or may not) be undesirable. In Chrome try removing that and submitting, then you'll see what I mean (I also made the "name" field required).
How can I prevent the enter key from submitting the form in angular?
Is there a way to catch the 13 key and disable it or set the form as invalid unless submitting from a button with ID of x?
Thanks
Since you have ng-click anyways, you could also use <button type="button">, even inside the form tag. The default behaviour of the button element is type="submit", which is what you want to prevent. So, no javascript needed at all!
Other users have already written that [button type="submit"] will cause this trouble. PLEASE NOTE: buttons WITHOUT any type="..." declaration are "submit" by default! So make sure you always use type="button".
After a couple hours, this weird code was the only thing that worked.
I'm waiting for better answers, won't accept this monster:
app.directive('onKeyup', function() {
return function(scope, elm, attrs) {
var allowedKeys = scope.$eval(attrs.keys);
elm.bind('keydown', function(evt) {
angular.forEach(allowedKeys, function(key) {
if (key == evt.which) {
evt.preventDefault(); // Doesn't work at all
window.stop(); // Works in all browsers but IE
document.execCommand("Stop"); // Works in IE
return false; // Don't even know why it's here. Does nothing.
}
});
});
};
});
and trigger it by using this on all form inputs:
<input on-keyup="bla" keys="[13]" .... />
For now, whenever the user press the enter key, the window try to submit, then fail to do so, not so silently. Ugly but it works.
Edit: keydown is a little better than keyup for the element bind, now enter key fails silently-ish
so simple, doesn't need to do anything. just add this to your form tag if you are using angular +2
<form (keydown.enter)="$event.preventDefault()" ...>
If you are attempting to prevent the form from being submitted on just a single element, you can add the following ng-keypress handler (this is for Angular 1.x):
<input type="text" name="myField" ng-keypress="keyPressHandler($event)"/>
With the following implementation for keyPressHandler:
$scope.keyPressHandler = function(e) {
if (e.keyCode === 13) {
e.preventDefault();
e.stopPropagation();
// Perform your custom logic here if any
}
}
I had a similar problem, I ended up taking the button out of the form.
Seeing as I use ng-click and everything is binded with ng-model it doesn't really matter if it's inside the form or not.
I realise this is bad practice but it sure as hell beats writing a custom directive to intercept keystrokes.
Check this:
if a form has 2+ input fields and no buttons or input[type=submit]
then hitting enter doesn't trigger submit
Thus if your form has 2+ input fields, you could use something like <span ng-click="submit()">Sumbit</span> to prevent key-trigger of enter key in those input fields.
I came across this issue. Yes, you would need to remove all type='submit' from your page, and make sure any other buttons have type="button" but then the challenge is still being able to use normal validation submission.
I created a directive that triggers form submission + form states for validation. Replacing:
<button type="submit">
with
<button submit-button type="button">
Directive:
export default /*#ngInject*/ function submitButton($log) {
return ({
require: '^form',
link: link,
restrict: 'A'
});
function link(scope, element, attributes, formCtrl) {
element.on('click', clickHandler);
function clickHandler() {
formCtrl.$setDirty(true);
formCtrl.$setSubmitted(true);
angular.element(element[0].form).triggerHandler('submit');
$log.info('Form Submitted');
}
}
You can still hit ENTER to submit when focused on your submit-button, better for UX and Accessibility I think.
The easiest solution to this I found is to use input type as button instead of submit and bind the form submit function with ng-click and not using the ng-submit in the form tag.
I hope this helps.
This is my weird but quick and simple solution without any directives.
HTML:
<form ng-submit='submitForm()'>
<input type='text'>
<button type='submit' ng-mousedown='doSubmit=true'>submit</button>
</form>
Controller:
$scope.submitForm = function() {
if (!$scope.doSubmit) {
return;
}
$scope.doSubmit = false;
console.log('execute some actions');
}
You can catch the default form submit in your controller using ng-submit on the form tag and it will prevent a submit:
http://docs.angularjs.org/api/ng.directive:ngSubmit
alternatively, if you really wanted to catch the key events, there are also directives for that which pass an event you can call stop:
http://docs.angularjs.org/api/ng.directive:ngKeyup
angular.element(document).ready(function () {
angular.element(window).keydown(function () {
if(event.keyCode == 13) {
event.preventDefault();
return false;
}
});
});
Try with this in angularjs controller
A form is submitted when the enter key is clicked while a control within the form has focus. If you register a listener using ng-submit you can intercept this and use prevent defaults to stop the default process (i.e. submitting the form). Have a look at th
The following should work . . . i.e., the form is only submitted on button click, and not on hitting Enter in the Input boxes. (This definitely works for reactive forms. I didn't test it for template forms).
<form #form [formGroup]="form" METHOD="GET" action="http://localhost:3000/test">
<input placeholder="Enter"/>
<input placeholder="The Dragon"/>
<button type="button" (click)="form.submit()">Submit</button>
</form>
Of course, remember all the imports and declarations:
app.module.ts
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
#NgModule({
imports: [
. . .
FormsModule,
ReactiveFormsModule
]
. . .
})
export class AppModule { }
test.component.ts
import { FormGroup, FormControl } from '#angular/forms';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent {
form: FormGroup = new FormGroup({});
constructor() { }
}
Try setting a variable when you click the submit button and checking that it has been set in the form submit.
$scope.click = function () {
$scope.clicked = true;
$scope.submit();
};
$scope.submit = function () {
if ($scope.clicked) {
... submit
} else {
... prevent defaults
}
$scope.clicked = false;
};
See jsfiddle
Angularjs is running my forms through the FormController (eg tracking pristine, dirty, etc). I don't need this functionality; I'm sure it's adding overhead to my $digests.
How can I shut it off?
AFAIK there is no simple switch to turn off AngularJS validation. Actually most of the validation happens in the NgModelController and input directives - basically code in the input.js file. So, to get rid of the built-in validation you would have to re-develop code from this file (plus some others, like select).
Did you identify validation code as a performance bottleneck in your application?
UPDATE : This does NOT work ... well at least not in a way you'd like it to. Adding ng-non-bindable to the form or any input breaks ALL binding. So, your ng-model in the inputs won't work anymore. Sorry ....
ng-non-bindable is the solution to this problem.
It will prevent AngularJS from seeing the form as a directive. This will make AngularJS ignore the entire form:
<form name="inviteContactForm" ng-non-bindable>
This will make AngularJS ignore one part of a form:
<input type="email" name="email" ng-non-bindable>
You can read a bit about my whining on this issue here. http://calendee.com/preventing-angularjs-from-hijacking-forms/
Internally Angular creates factories out of directives by adding the Directive suffix to the directive name. So you can replace the validation and input directive factories with no-operational ones.
var noopDirective = function() { return function () {}; };
angular.module('myModule')
.factory('requiredDirective', noopDirective)
.factory('ngRequiredDirective', noopDirective)
.factory('inputDirective', noopDirective)
.factory('textareaDirective', noopDirective); // etc...
Similar to previous answer from #Chui Tey, having a directive that requires 'ngModel'. This is what I did
for disabling validations in runtime:
//disabling all validators
forEach(ngModelController.$validators, function(validator, validatorName){
ngModelController.$setValidity(validatorName, true);//mark invalid as valid
var originalValidator = ngModelController.$validators[validatorName]; //we save the original validator for being able to restore it in future
ngModelController.$validators[validatorName] = _.wrap(true);//overwrite the validator
ngModelController.$validators[validatorName].originalValidator = originalValidator;
});
//for restoring validations
forEach(ngModelController.$validators, function(validator, validatorName){
if(ngModelController.$validators[validatorName].originalValidator){
ngModelController.$validators[validatorName] = ngModelController.$validators[validatorName].originalValidator;
}
});
ngModelController.$validate(); //triger validations
A colleague suggested a nifty way of disabling validators. Here's an implementation:
<input type="radio" name="enableValidation" ng-model="$ctrl.validationEnabled" ng-value="true" />Enabled
<input type="radio" name="enableValidation" ng-model="$ctrl.validationEnabled" ng-value="false" />Disabled
<input type="number"
name="age"
ng-model="$ctrl.age"
min="20"
disable-validation="!$ctrl.validationEnabled" />
When disable-validation is true, then all validation rules automatically passes.
function disableValidation(scope, elem, attrs, ngModelController) {
function wrapOriginalValidators() {
var originalValidators = angular.copy(ngModelController.$validators);
Object.keys(originalValidators).forEach(function(key) {
ngModelController.$validators[key] = function(modelValue, viewValue) {
return scope.$eval(attrs.disableValidation) || originalValidators[key](modelValue, viewValue);
}
});
}
function watchDisableCriteria() {
scope.$watch(attrs.disableValidation, function() {
// trigger validation
var originalViewValue = ngModelController.$viewValue;
scope.$applyAsync(function() {
ngModelController.$setViewValue('');
});
scope.$applyAsync(function() {
ngModelController.$setViewValue(originalViewValue);
});
});
}
wrapOriginalValidators();
watchDisableCriteria();
}
angular.module('app', [])
.directive('disableValidation', function() {
return {
require: 'ngModel',
link: disableValidation
};
});
Obviously, you'd not use this for performance reasons, but when you
need to dynamically enable or disable validations.
Sample: https://plnkr.co/edit/EM1tGb