How to submit a form in Angular - javascript

I have an html form that I am validating with an Angular controller. If the validation fails, I apply certain classes to the html. If it passes, I want to let the form submit itself. Although this seems very straightforward, I haven't found an easy way to do this. One method I have found uses the $scope.$broadcast function to tell the form to submit, however, I am using the Controller as Ctrl syntax so I would rather not use $scope. Is there anyway to submit a form from a controller?
My simplified HTML
<form ng-controller="LoginCtrl as login" ng-submit="login.validate()" method="post">
<input ng-model="login.username" />
<input ng-model="login.password" />
</form>
JS
var app = angular.module("app", []);
app.controller("LoginCtrl", ["$http", function($http) {
this.username = "";
this.password = "";
this.validate = function() {
//validate
if (valid) {
// somehow form.submit()
}
};
}]);
I am somewhat new to Angular so forgive me if this is an obvious quesion ;)
EDIT:
I need to clarify that I am looking to avoid submitting the form with AJAX (i.e. $http.post). Basically what I want is the controller equivalent of calling form.submit().
USE CASE:
Let me explain exactly what I am trying to do.
User arrives at login page
User enters credentials
User hits Submit
Controller asks server (using api path) if the credentials are valid
if valid then
Tell the form to submit to regular login path // how?
else
Immediately tell the user they submitted invalid credentials
This way the User gets immediate feedback if they entered incorrect credentials.
All of this I have implemented except for the actual form submission.

Simplest approach would be wrap all the form element data into one object. You don't have to create this object if you have no data to prepopulate, ng-model will create it for you.
<form ng-controller="LoginCtrl as login" ng-submit="login.validate()" method="post">
<input ng-model="login.MyFormData.username" />
<input ng-model="login.MyFormData.password" />
</form>
This will result in an object in your controller scope looking like:
$scope.MyFormData={
username :'foo',
password:'bar'
}
When ready to submit:
$http.post('path/to/server', $scope.myFormData).success(response){
/* do something with response */
})

I have an example with the bare minimum code here. Note, it is self validating, and you don't even need to submit anything from the COntroller! you can include the action and method fields as form attributes, and angular will submit the form if it is valid
HTML
<form name="LoginCtrl as loginForm" method="Post" action="not-a-real-script.php">
<input type="text" name="name" ng-model="loginData.name" placeholder="username" required="" />
<input type="password" name="password" ng-model="loginData.password" placeholder="Password" required />
<input type="submit" ng-disabled="loginForm.$invalid" value="Login" />
</form>
JS
angular.module('app', [])
.controller('LoginCtrl', function($scope) {
$scope.loginData = {};
});

I think you want to have validation as part of your form submission flow. Try something like this:
HTML
<form ng-controller="LoginCtrl as login" ng-submit="login.submit()" method="post">
<input ng-model="auth.username" />
<input ng-model="auth.password" />
<div class="error" ng-hide="valid">Something is invalid...</div>
</form>
JS
var app = angular.module("app", []);
app.controller("LoginCtrl", ["$http", "$scope", function($http, $scope) {
$scope.valid = true;
$scope.auth.username = "";
$scope.auth.password = "";
var valid = function() {
// validate
return valid; // true or false
};
this.submit = function() {
if (valid()) {
$http.post('/your/auth/url', { auth: auth }).success(function(response) {
// whatever you're doing to store the auth response.
});
} else {
// use this to conditionally show error messages with ng-show
$scope.valid = false;
}
};
}]);
I'm not sure I understand your comment about using the controller-as syntax. That shouldn't change how you use $scope.

Related

How to mimic a HTML Form request in Angularjs?

I made an app using Laravel so most of the pages uses simple HTML Form to send HTTP requests.
I recently decided to use Angular Material to code my front-end, but as all of the input components forces to use ng-model, I want to mimic the behavior of the simple HTML Form Submission using an Angular Controller.
For example: I want this
index.html
<form action="/confirm" method="POST">
<input type="text" name="name">
<input type="submit" value="Submit">
</form>
By using this
index.html
<input type="text" name="name" ng-model="name">
<form action="/confirm" method="POST" ng-submit="submit($event)">
<input type="submit" value="Submit">
</form>
app.js
var app = angular.module('MyApp', []);
app.controller('AppController', ['$scope', function($scope){
$scope.submit = function(e){
e.preventDefault();
var url = e.currentTarget.getAttribute('action');
var data = {
name : this.name
};
// Some code here that I can't figure out
// Which will mimic the simple HTML Form Submission
};
}]);
One solution is to append a hidden input inside the form for each of the inputs outside the form. I don't want to do it that way, it will be very in-efficient.
Any other ways? Thank You.
And it will be great if anyone knows how to handle this for a file input.
Javascript:
app.controller("MainCtrl", ["$scope", "$http", function($scope, $http) {
$scope.name = null;
$scope.submitForm = function(name) {
//submitting form items depend on what your /confirm endpoint is expecting
//is it expecting JSON, form data in the body, etc?
//JSON
var payload = {
name: name
};
//submitting via FormData
//var payload = new FormData();
//payload.append("name", name);
//use $http to post to server
$http.post('/confirm/', payload).then(function(response) {
//success
}, function() {
//an error has occurred
});
}
}]);
HTML:
<form id="myForm" name="myForm" ng-submit="submitForm(name)">
<!-- bind model to input using ng-model -->
<input required type="text" ng-model="name" />
<button ng-disabled="myForm.$invalid" type="submit" ng-click="submitForm(name)">Submit</button>
</form>
You could use the $http service to make http calls you code should look like this
var url = e.currentTarget.getAttribute('action');
var data = {
name : this.name
};
$http({
method: url,
url: '/confirm',
data:data
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});

apply angular validations only after submit and not to show validations, if user removed text from text box after submit

Apply angular validations only after submit and not to show validations, if user removed text from text box after submit.
my requirement is technically,
--> if user entered text it should not show any validation
--> show validations only after if user clicked on submit
&
--> after submit if user touched that text box (or) removed text from text box then validation msgs should not show
can you guys please give me solution,
fast replies are appreciated, If you provide me fiddles then i would be very thankful.
thanks in advance
You can solve the problem by using a flag. Assume you have a form that contains an input. You have to set the flag isSubmitted to true when you clicked the submit button. and make it false when your input changes. Then show your validation message based on that flag:
Here is a hypothetical controller:
function appCtrl($scope) {
$scope.isSubmitted = false;
$scope.submit = function() {
$scope.isSubmitted = true;
//...
}
$scope.inputChanged = function() {
$scope.isSubmitted = false;
}
}
And this can be a part of your view:
<input name="inputName" type="text" ng-model="myModel" ng-change="inputChanged()" required="" />
<span ng-show="form.$error.required && form.$dirty && isSubmitted">name is required</span>
I don't know exactly what you are looking for, but I guess this piece of code could be useful.
It's just a basic form with its controller and some validated field.
Each field is required in this example, but as you can see no validation feedback is given to the user.
The validation pass only if the form is valid and then the submission is registered with a flag ( true/false ).
If the flag is true and the user touch one of the fields, all fields are blank again.
The Controller
(function(){
var Controller = function($scope){
// Parameters
var isFormSubmitted = false;
// Methods
// Shared Methods
$scope.checkFocus = function(){
if(!isFormSubmitted){
return;
}
// Reset all fields
$scope.fields = null;
// Do something
}
$scope.validate = function(){
isFormSubmitted = true;
// Do some validation
};
};
Controller.$inject = [
'$scope'
];
app.controller('MainCtrl', Controller);
})();
The view
<div ng-controller="MainCtrl">
<form name="form" ng-submit="form.$valid && validate()" novalidate>
<input ng-focus="checkFocus()" ng-model="fields.username" type="text" required placeholder="Username">
<input ng-focus="checkFocus()" ng-model="fields.password" type="password" required placeholder="Password">
<input type="submit" value="Submit">
</form>
</div>

How do I reset a form in angularjs?

See Fiddle: http://jsfiddle.net/hejado/7bqjqc2w/
I'm trying to form.reset() my form using angular.
HTML:
<div ng-controller="formCtrl">
<form name="resetme" id="resetme">
<input ng-model="text" type="text" />
<input file-model="file" type="file" />
<button type="button" ng-click="resetForm()">reset</button>
</form>
</div>
JS:
.controller('formCtrl', function($scope) {
$scope.resetForm = function() {
//$scope.resetme.reset();
document.getElementById('resetme').reset();
};
});
Please note: I'm using this kind of form to ajax-upload a file. The page is not refreshing and I don't want to use any reset-buttons. (I'm using one in the fiddle for simplicity.) I want to call the reset-function after the fileupload is finished (via http success).
I'm using
<input type="file" />
so I can't reassign empty values to all my inputs, because file inputs are readonly.
Calling the reset() function on the DOM element works, but I was told talking to the DOM in angular would be evil, so...
I'd like to know, how this would be done the angular way. I tried naming the form and referencing it via $scope.formname but I'm not able to call Web API functions... (commented line)
How can I achieve this?
UPDATE
After reading some of the answers, I should make clear, that I am using ngModel and a custom directive fileModel to get a hold of the file-object.
Some of the solutions worked in resetting the value of the input field, but the model is not cleared (neither file, nor text). Custom directives are the answer to that, but this kinda exceeds the scope of this question.
I wrote about this topic a couple years ago. I don't know if the Angular team has yet implemented a native form reset directive but you can do so yourself. There are a couple caveats to this implementation: it only works for one model (if you need to support more see the followup post) and the issue of when to initialize the original values. Also, I never tested this with file inputs so I am not sure it would work with those.
There was an issue for this but it was closed due to inactivity. :/
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope',
function($scope) {
$scope.myModel = {
foo: 'Boop',
bar: 'Beep'
};
$scope.myModelCopy = angular.copy($scope.myModel);
}
]);
myApp.directive('resetDirective', ['$parse',
function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr.resetDirective);
var masterModel = angular.copy(fn(scope));
// Error check to see if expression returned a model
if (!fn.assign) {
throw Error('Expression is required to be a model: ' + attr.resetDirective);
}
element.bind('reset', function(event) {
scope.$apply(function() {
fn.assign(scope, angular.copy(masterModel));
scope.form.$setPristine();
});
// TODO: memoize prevention method
if (event.preventDefault) {
return event.preventDefault();
} else {
return false;
}
});
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<form reset-directive="myModel" name="form">
<input type="text" ng-model="myModel.foo" />
<input type="text" ng-model="myModel.bar" />
<input type="reset" value="Reset" />
<pre>myModel: {{ myModel | json }}</pre>
<pre>myModelCopy: {{ myModelCopy | json }}</pre>
<pre>form pristine: {{ form.$pristine }}</pre>
</form>
</div>
</body>
You can try :
reset
$scope.resetForm = function(form) {
//Even when you use form = {} it does not work
form.fieldA = null;
form.fieldB = null;
///more fields
}
Or
$scope.resetForm = function(form) {
//Even when you use form = {} it does not work
angular.copy({},form);
}
See Demo
You'd want to attach ng-model to each of your input fields then null them out via $scope. Either that or make a custom directive
I've just had a similar problem with forms not resetting. Here's what I would do:
In your resetform() function, I would include statements that set both of your ng-models in your input to "". For example:
**HTML**
<input ng-model="text" type="text" />
<input file-model="file" type="file" />
**JS**
.controller('formCtrl', function($scope) {
$scope.resetForm = function() {
$scope.text = "";
$scope.file = null;
};
});
Not certain if this will work for file-models but I'm certain it will remove the text. Best of luck!
If you don't want to use ng-model and proper reset type of button you can use this code, however this is not proper angular way to reset the form but it will work
$scope.reset = function(){
$('form').children('*').each(function(){
$(this).val('');
});
}
Here's the Plunker
To reset the form data use following code :
$scope.resetEmployeeData = function() {
$scope.employeeCred.userName = '';
$scope.employeeCred.employeeNo = '';
$scope.employeeCred.sapNo = '';
$scope.employeeCred.emailD = '';
$scope.employeeCred.mobileNo = '';
**this**.createEmployee.$setPristine();
**this**.createEmployee.$setUntouched();
};
use this rather than $scope.

ng-show/ng-hide loops when a "submit()" added, making the browser crash

I am trying to add an error message with an either ng-show or ng-hide.
<p ng-show="submit()">Incorrect login/password.</p>
<form method="POST" name="adminform" ng-submit="submit()" class="admin-login-form">
<label for="username">Admin Login</label>
<input ng-model="username" type="text" name="username" class="form-control" required autofocus="yes"/>
<label for="password">Password</label>
<input ng-model="password" type="password" name="password" class="form-control" required />
<input type="submit" class="form-control" name="submit" value="Login" required/>
</form>
Ideally, the show/hide needs to be activated when submit() was run and did not succeed on login. However, when I do ng-show="submit()" it works but keeps refreshing itself and looping, making the browser crash and giving an error in the console even if the submit button itself hasn't been clicked.
$scope.submit = function() {
var data = {
'username': $scope.username,
'password': $scope.password
};
var hookphp = loginService.login();
hookphp.save(data,
function(result){
$location.path("dashboard");
},
function(){
alert('Invalid password/login.');
}
);
};
Also, it never gives me the alert when the login is not successful. Could anyone suggest how to do this properly? I am still in the process of figuring out how Javascript and Angular work. Thanks a lot!
<p ng-show="errorVisible">Incorrect login/password.</p>
In angular
$scope.errorVisible = true/false (set it whenever you need it)
So:
$scope.errorVisible = false;
$scope.submit = function() {
var data = {
'username': $scope.username,
'password': $scope.password
};
var hookphp = loginService.login();
hookphp.save(data,
function(result){
$location.path("dashboard");
$scope.errorVisible = false; //optional
},
function(){
$scope.errorVisible = true;
}
);
};
Also the reason it's looping forever in your initial code is that ng-show keeps calling the function, but the function doesn't return anything.
On your code, it seems like your function is not returning either true or false depending on the login status, so in that case, since function call is success, the output of the function will always be true causing your login failure label will be visible at all times.
As #MrVentzi suggested, it is better to use separate scope variable in order to decide wheter to show the message or not. If you try to do it as
<p ng-show="submit()">Incorrect login/password.</p>
ng-show will be constantly calling and listening to output of the function. In order to show you better, I created this example: http://jsfiddle.net/eytt2d8x/

Angularjs Chrome autocomplete dilemma

I have a simple login form which works just peachy unless you use Chrome's auto complete feature.
If you start typing and use the auto complete feature and it auto populates your password, my angularjs model does not have any value for the password.
I tried to turn autocomplete off by setting the attribute on the form autocomplete="off" but that doesn't seem to have any effect.
How can I either:
1. Ensure that I can get the value if someone uses Chrome's auto-complete feature?
2. Disable Chrome's auto-complete feature?
<form class="form-signin" name="form" ng-submit="login()" autocomplete="off">
<h3>Login</h3>
<input type="email" name="email" class="form-control" placeholder="Email address" ng-model="user.email" required autofocus>
<input type="password" name="password" class="form-control" placeholder="Password" ng-model="user.password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
From the link added in the comment:Github Issue's
// Due to browsers issue, it's impossible to detect without a timeout any changes of autofilled inputs
// https://github.com/angular/angular.js/issues/1460
// https://github.com/angular/angular.js/issues/1460#issuecomment-28662156
// Could break future Angular releases (if use `compile()` instead of `link())
// TODO support select
angular.module("app").config(["$provide", function($provide) {
var inputDecoration = ["$delegate", "inputsWatcher", function($delegate, inputsWatcher) {
var directive = $delegate[0];
var link = directive.link;
function linkDecoration(scope, element, attrs, ngModel){
var handler;
// By default model.$viewValue is equals to undefined
if(attrs.type == "checkbox"){
inputsWatcher.registerInput(handler = function(){
var value = element[0].checked;
// By default element is not checked
if (value && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}else if(attrs.type == "radio"){
inputsWatcher.registerInput(handler = function(){
var value = attrs.value;
// By default element is not checked
if (element[0].checked && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}else{
inputsWatcher.registerInput(handler = function(){
var value = element.val();
// By default value is an empty string
if ((ngModel.$viewValue !== undefined || value !== "") && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}
scope.$on("$destroy", function(){
inputsWatcher.unregisterInput(handler);
});
// Exec original `link()`
link.apply(this, [].slice.call(arguments, 0));
}
// Decorate `link()` don't work for `inputDirective` (why?)
/*
directive.link = linkDecoration;
*/
// So use `compile()` instead
directive.compile = function compile(element, attrs, transclude){
return linkDecoration;
};
delete directive.link;
return $delegate;
}];
$provide.decorator("inputDirective", inputDecoration);
$provide.decorator("textareaDirective", inputDecoration);
//TODO decorate selectDirective (see binding "change" for `Single()` and `Multiple()`)
}]).factory("inputsWatcher", ["$interval", "$rootScope", function($interval, $rootScope){
var INTERVAL_MS = 500;
var promise;
var handlers = [];
function execHandlers(){
for(var i = 0, l = handlers.length; i < l; i++){
handlers[i]();
}
}
return {
registerInput: function registerInput(handler){
if(handlers.push(handler) == 1){
promise = $interval(execHandlers, INTERVAL_MS);
}
},
unregisterInput: function unregisterInput(handler){
handlers.splice(handlers.indexOf(handler), 1);
if(handlers.length == 0){
$interval.cancel(promise);
}
}
}
}]);
From: Developer.mozilla.org docs Turning_off_form_autocompletion
If an author would like to prevent the auto-filling of password fields
in user management pages where a user can specify a new password for
someone other than themselves, autocomplete="new-password" should be
specified, though support for this has not been implemented in all
browsers yet.
So, what makes it work for me:
set autocomplete="new-password" on the password field
set autocomplete="off" in the username field.
I hope that it works for you too :)
As said here, https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
The Google Chrome UI for auto-complete requests varies, depending on
whether autocomplete is set to off on input elements as well as their
form. Specifically, when a form has autocomplete set to off and its
input element's autocomplete field is not set, then if the user asks
for autofill suggestions for the input element, Chrome might display a
message saying "autocomplete has been disabled for this form." On the
other hand, if both the form and the input element have autocomplete
set to off, the browser will not display that message. For this
reason, you should set autocomplete to off for each input that has
custom auto-completion.
You need to set autocomplete="off" on both form and input
I don't think this is related to AngularJS
I had the same issue and found a very simple solution that just uses jQuery to grab the value on submit. In my controller I have the following:
$scope.username = "";
$scope.password = "";
$scope.login = function(){
$scope.username = $("#username").val();
$scope.password = $("#password").val();
// Proceed as normal
};
There are some downsides, if you need to do validation etc but otherwise it's fine for smaller forms like this.
You could watch the email field value and everytime the value in that field is changing, you could trigger a "change"-event on the password field. This events trigger all the ng-model magic on that field and updates the model.
module.directive("autocompleteFor", function () {
return {
restrict: "A",
link: function ($scope, $element, $attrs) {
$scope.$watch($attrs.autocompleteFor, function () {
$element.triggerHandler("change");
})
}
}
});
With this directive you could handle that scenario like this:
<input type="email" name="email" ng-model="user.email">
<input type="password" autocomplete-for="user.email" name="password" ng-model="user.password" required>
-----------------------------
To disable the autocomplete/autofill from a input, just type:
- autocomplete="false" instead of autocomplete="off"!
Below directive worked for me. It's simple and clean fix. Hope that helps!
Ref: AngularJS browser autofill workaround by using a directive
Here is a solution that is far less hacky than other solutions presented and is semantically sound AngularJS: VictorBlog.com
myApp.directive('formAutofillFix', function() {
return function(scope, elem, attrs) {
// Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
elem.prop('method', 'POST');
// Fix autofill issues where Angular doesn't know about auto-filled inputs
if(attrs.ngSubmit) {
setTimeout(function() {
elem.unbind('submit').submit(function(e) {
e.preventDefault();
elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
scope.$apply(attrs.ngSubmit);
});
}, 0);
}
};
});
Then you simply attach the directive to your form:
<form ng-submit="submitLoginForm()" form-autofill-fix>
<div>
<input type="email" ng-model="email" ng-required />
<input type="password" ng-model="password" ng-required />
<button type="submit">Log In</button>
</div>
</form>
alternative solution is just to get rid off form element and use ng-form instead, it disables all browser interferings
<div ng-form="yourFormName" class="form-signin" ng-submit="login()">
Old question, but whatever
I came across the same problem and I've got a small "hack" solution. This problem happened at many different places in my app, so I created a directive for reusability.
module.directive("fakeAutocomplete", [
function () {
return {
restrict: "EA",
replace: true,
template: "<div><input/><input type=\"password\"/></div>",
link: function (scope, elem, attrs) {
elem.css({
"overflow": "hidden",
"width": "0px",
"height": "0px"
});
}
}
}
]);
And simply add
<fake-autocomplete></fake-autocomplete>
At the beginning of your form and the browser will detect the fake fields as the ones that should autocomplete. Simply putting display:none on the fields also does not seem to work anymore, I've tested it.
In my case, i set property autocomplete="off" in form and input.
<form autocomplete="off">
<input type="text" autocomplete="off">
</form>
It could be much simpler solution to the problem.
Angularjs couldn't "see" the value
Take the value via DOM (jQuery) then put it back into Angularjs.
```
angular.module('someModule').directive('chromeAutofillHack', function()
{
return {
require: '^ngModel',
restrict: 'A',
priority: 500, // set higher priority then other custom directives
link: function(scope, element, attrs , ngModelCtrl)
{
ngModelCtrl.$parsers.unshift(function(email)
{
if (!email) { // only do this when angular think there is no value
email = $(element).val();
ngModel.$setViewValue(email);
}
return email;
});
}
};
});
```
--- NO LONGER RELEVANT ---
I was able to disable autocomplete (weirdly enough) by adding the following.
<form ... novalidate>
<input ... formnovalidate />
Reference this Plunker
My solution for Chrome 35.0, Firefox 30.0, angular 1.2.18 (login page with password manager, autofill, angular method and redirect):
How does browser know when to prompt user to save password?
I ended up with a different solution that I don't see here yet. From what I found, the password value isn't exposed to the model (or possibly even the js api) until the user interacts with the page. Clicking the login button is enough interaction to make the value available, and the data binding will succeed early enough for the click handler on the button to access the password from the model. So if I could detect that the browser has auto-filled, I could enable the login button even though my model hadn't been updated yet. So I wrote a simple helper service to see if Chrome has auto-filled any inputs:
utilApp.service('autoFillDetectionService', [function () {
return {
hasAutoFillInputs: function () {
try{
return !!$(':-webkit-autofill').length;
}
catch (x) {
// IE gets here, it/jquery complains about an invalid pseudo-class
return false;
}
}
};
}]);
From the login controller, I have an interval checking if any input fields are marked as autofill and if so enable the login button.
Just Replace autocomplete="off" with autocomplete="new-password".

Categories

Resources