Inputfield unfocus after typing 1 character - html

I can't seem to write down a full word without being unfocused after each character I type in the inputfield. Trying to understand why that is.
AngularJS
var module = angular.module("myModule", []);
module.controller("myController", function($scope) {
$scope.prop = {};
});
HTML
<div ng-app="myModule">
<div ng-controller="myController">
<button ng-show="!prop.dropdownType"
ng-click="prop.dropdownType = ['']">Init</button>
<div ng-hide="!prop.dropdownType" ng-repeat="(key, value) in prop.dropdownType">
<input type="text" ng-model="prop.dropdownType[key]">
</div>
<button ng-hide="!prop.dropdownType"
ng-click="prop.dropdownType.push('')" >Add options</button>
</div>
</div>
EDIT: created a quick code where you can see what i mean. Just run the code, initialize the inputfield and try to type a word: https://jsfiddle.net/wk173q0a/

I was able to fix your code by making the following change:
<div ng-hide="!prop.dropdownType">
<input ng-repeat="type in prop.dropdownType track by $index" type="text" ng-model="type">
</div>
The problem is that you are updating the key for the values in which you are iterating over. This is kicking off a digest cycle and you are losing focus. Also, the add button did not work because you were adding identical objects with no tracking.
Lastly, you will want to iterate over an array of objects to be able to maintain the reference in ng-model. Otherwise, all the changes will be lost once you add a new value to your array.
This is a great read on understanding the digest cycle:
https://www.thinkful.com/projects/understanding-the-digest-cycle-528/

This is happening because you are updating the list/object that controls your ng-repeat.
prop.dropdownType may start as [''], but as soon as you type into your input, you are updating the prop.dropdownType object. AngularJS sees that you have changed the prop.dropdownType and it refreshes the dom with the new input. If you typed the character A, the prop.dropdownType will now have a key of A (and a value of null?) and the input you see is now a different object.
If you change your ng-model to be a separate array or some other property, this issue should go away.

This is happening because of ng-repeat in tag. So, in this case try below 2 methods to solve the input element focus issue.
1) Use track by $index
<div ng-hide="!prop.dropdownType" ng-repeat="(key, value) in prop.dropdownType track by $index">
<input type="text" ng-model="prop.dropdownType[$index]">
</div>
2) Wrap your strings into objects. E.g. prop.dropdownType = [{value: 'string1'}, {value: 'string2'}, ...]:
<div ng-hide="!prop.dropdownType" ng-repeat="(key, value) in prop.dropdownType">
<input type="text" ng-model="prop.dropdownType[$index].value">
</div>

Related

append String to ngModel to get an expression

I have a Business Object Car{id:2,name:ford,modelNo:123} I also have a MetaData class for CarMetaData{columnName:string,type:text,required:true}
each attribute has it's own CarMetaData object
how can I display Car data in a form in such a way using an Array of CarMetaData objects
In the template.html file
<form #carForm='ngForm'>
<div class="form-group" *ngFor="let metaData of metadata" >
<input [(ngModel)]="car[metaData.columnName]" name="metaData.columnName"
type="text">
</div>
</form>
In the component file
car:Car;
metadata:CarMetaData[];
The above method isn't working because of the [] in car[meta.columnName] in the
ngModel is there a way an expression can be worked out in *ngFor or [(ngModle)] to calculate columName
Very stupid mistake. Please try this:
<div *ngFor="let meta of metaData;">
<input [(ngModel)]="car[meta.columnName]" name="{{meta.columnName}}" />
{{meta.columnName}}
</div>
Old answer:
But first - your loop (meta in metaData) is wrong as you loop array, not object. So you better do let meta of metaData, let i = index.
And the interesting part is how ngModel sets the value of inputs in ngFor. If you try this:
<div *ngFor="let meta of metaData; let i = index">
<input [(ngModel)]="car[meta.columnName]" name="meta.columnName" />
{{meta.columnName}}
</div>
You will see that input value is the same of all inputs (it was picked from the first ngFor iteration and repeated to all inputs). However, {{meta.columnName}} prints correct values. So there is some scoping issue. Other strange thing - its definitely related with ngForm and input's name property. If you move that outside of the form - all happens as expected. And if you:
<div *ngFor="let meta of metaData; let i = index">
<input [(ngModel)]="car[meta.columnName]" name="{{metaData[i].columnName}}" />
{{meta.columnName}}
</div>
Inside the form - again, all works well. So that might be your workaround.
Here is a DEMO. Hopefully someone will explain it further.
I'd suggest to put both objects in a wrapper-object. e.g.
export class CarWrapper {
constructor(
car: Car,
carMetaData: CarMetaData
) {}
}
Then build a method that joins both objects in this wrapper
private carWrapper: Array<CarWrapper> = [];
private fillCarWrapper(): void {
// your code here
}
And in your template you act like this
<form #carForm='ngForm'>
<div class="form-group" *ngFor="let wrapper of carWrapper">
<input [(ngModel)]="wrapper.car" name="wrapper.carMetaData.columName" type="text">
</div>
</form>

how to bind component variable to form object instance property

I am not quite sure how to bind a variable from my component class to a property value in my form object. The form needs to display this value and add it to the ngModel so that it can become part of the object instance.
I am struggling with the syntax and keep getting the errorNo value accessor for form control with name: 'per_print'
Error: No value accessor for form control with name: I think I need to use the [(ngModel)]="myObject.property" syntax, but I am not getting this from an input into the form, but from a binded variable on the class.
<div class="form-group">
<label class="label-text" >Per Print</label>
<div class="input-group" style="width:150px;">
<h4 [(ngModel)]="job_entry.per_print" name="per_print"
value='pricePerPrint' class="form-control"
>
{{pricePerPrint | currency:'GBP':true:'1.2-2'}}
</h4>
</div>
</div>
job_entry is my object which properties I am setting through the form. pricePerPrint is a variable on the class. I want to set this variable to one of the form instance properties. How to do this? I thought I could do it through value, as in the value of the <h4> tag, but this error persists.
You could use [hidden] input field with the value you want, so that this value will be added to your form. This means though, that you need to use pricePerPrint as the ngModel. But ngModel for your job_entry is possibly not needed. You could build the form as such, so that the object you get from the form can be assigned directly to job_entry:
onSubmit(obj) {
this.job_entry = obj;
}
Also check the Demo for that.
So your code would look like this:
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm.value)">
<input [hidden]="isHidden" name="per_print"
[(ngModel)]="pricePerPrint" [value]="pricePerPrint"/>
<h4>Price: {{pricePerPrint}}</h4>
<button type="submit">Submit</button>
</form>
where isHidden is set to true.
Other option I see, if you want to use [(ngModel)]="job_entry.per_print, you need to assign whatever value you have in pricePerPrint to job_entry.per_print.

ANGULARJS: Function I have in ng-checked directive runs infinitely

Trying to do something I thought is pretty simple but it's turning out to be pretty annoying. I'm just trying to have a function that runs when you click on a checkbox using the ng-checked directive.
This is the HTML:
<div class="form-group">
<label class="col-sm-2 control-label">Make Payment Optional</label>
<div class="col-sm-4 center-checkbox">
<input type="checkbox"
class="center-checkbox"
ng-model="formData.optionalPayment"
ng-checked="optionalPaymentCheckbox();"
validate-servererror="featured"/>
</div>
</div>
And this is the Angular:
if($scope.formData.optionalPayment === undefined) {
$scope.formData.optionalPayment = TournamentConst.PAYMENT.OPTIONAL;
}
(This check is just for when I load the page for the first time.)
$scope.optionalPaymentCheckbox = function () {
if($scope.formData.optionalPayment === TournamentConst.PAYMENT.OPTIONAL) {
$scope.formData.optionalPayment = TournamentConst.PAYMENT.MANDATORY;
} else {
$scope.formData.optionalPayment = TournamentConst.PAYMENT.OPTIONAL;
}
};
When I load the page, this ng-checked function runs infinitely. Is there something about the ng-checked directive I don't know, or some minor detail or forgot? Thanks in advance.
You are misunderstanding the intention of ng-checked. What you think it does is "execute this expression when the checkbox is checked" - an event handler directive.
What it actually does is set the checked property based on the expression. This means it sets up a watch on the expression and evaluates it every digest. If the value changes, it sets or unsets the checked property accordingly.
In fact, the documentation for ng-checked says this:
Note that this directive should not be used together with ngModel, as this can lead to unexpected behavior.
As #JB Nizet correctly pointed out, you can achieve the desired effect in your particular case by using ng-true-value and ng-false-value and removing ng-checked altogether.
So your HTML becomes:
<div class="form-group">
<label class="col-sm-2 control-label">Make Payment Optional</label>
<div class="col-sm-4 center-checkbox">
<input type="checkbox"
class="center-checkbox"
ng-model="formData.optionalPayment"
ng-true-value="TournamentConst.PAYMENT.MANDATORY"
ng-false-value="TournamentConst.PAYMENT.OPTIONAL"
validate-servererror="featured"/>
</div>
</div>
Then, in your controller, populate your TournamentConst object in the scope, so the template can see it:
$scope.TournamentConst = TournamentConst;
(or you can just populate the bits you need)
Finally, get rid of the whole $scope.optionalPaymentCheckbox function. You will still need the code to set the default value, though.
One last thing: It is confusing that the property is called optionalPayment, when it is really more like paymentType, but that is not related to the current problem.

AngularJS ng-modal do not return latest value from form input

I am still new towards AngularJS, I made a simple textarea to handle user input using angular model binding like below code (noted that my ng-app and ng-controller are being injected somewhere else but it is within the entire <div></div>):
HTML:
<div ng-controller="StatusCtrl">
//some other HTML
<div class="sPTabs-holder">
<tabset>
<tab heading="Status">
<div>
<form class="statusPost" enctype="multipart/form-data">
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<textarea type="text" ng-model="inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
</div>
</div>
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<button style="width: 12%;" ng-click="postStatus()" class="btn btn-primary btn-sm" type="button">Share</button>
</div>
</div>
</form>
</div>
</tab>
<tab heading="Image">Image</tab>
</tabset>
</div>
</div>
JS:
'use strict';
var Status = angular.module('Status',['ui.bootstrap','ngResource','ngSanitize'])
Status.controller('StatusCtrl', ['StatusService','$resource','$scope','$http', '$timeout', '$sce',
function StatusCtrl(StatusService, $resource, $scope, $http, $timeout, $sce) {
//Usable models
$scope.inputStatus;
//Html-bind
$scope.makeTrust = function(html){
return $sce.trustAsHtml(html);
}
$scope.postStatus = function(){
if ($scope.inputStatus == null){
console.log('Blank post alert');
alert('You cannot post with blank statuses!');
}else{
console.log($scope.inputStatus);
}
}
}]);
My problem is whenever I click on the submit button angular will always pop me with the empty input error even though I have input in the textarea. At first I thought that I made a mistake in my model binding so I have tried out to echo the value in html using {{inputStatus}}, things appeared as it was typed and also when I try to define a default value in $scope.inputStatus = 'default value', the console does indeed echoed 'default value', but the problem is it doesn't store anything that is being typed in the form. What have i done wrong in my code?
Noted that I am not so familiar on how to setup AngularJS in JSFiddle. I apologize in advance if you would like to see the working demo.
**Update 1 - I have narrow down the problem, apparently the problem only occur when I am using angular tabs by Angular Bootstrap. So what happen is if you revise the HTML code, there is this <tabset> section. When declaring the ng-controller after the <tabset> section and everything works like a charm but if you declare it before the <tabset> section, that is where everything mess up.
You should initialize $scope.inputStatus in your controller, otherwise it will pop out an alert windows if you haven't input anything in the textarea (which will initialize or update $scope.inputStatus).
So you change your controller to
$scope.inputStatus = "";
Then everything will work, here is a working demo.
update
If you are using <tabset>, then you are facing child scope problem. <tabset> will create a child scope inside your controller, which means, the scope bind to tabset is the child of scope bind to StatusCtrl.
There are two ways to fix this problem. The first one is accessing the parent scope directly by changing your ngModel to below
<textarea type="text" ng-model="$parent.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
The second one is easier but may looks like a trick, use Dot notation like #lcycook mentioned. In your controller StatusCtrl, declare a dictionary called data
$scope.data = {
inputStatus: ""
};
Then you can access the inputStatus by data.inputStatus anywhere inside the controller scope and you don't need to care about the child scope.
While there is no direct evidence, I suspect your text area is masked inside a child scope. This is common for new AngularJS developers.
While you are learning which directive creates a child scope (e.g. ng-if, ng-repeat), you can avoid this problem with "Dot notation". Which is, wrapping the model inside an object.
You can do this by initializing your ng-model or at least the wrapper object in your controller.
$scope.data = {};
// OR
$scope.data = {inputStatus=''};
Then in your template
<textarea type="text" ng-model="data.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
Process it in your controller by referring it as $scope.data.inputStatus.
Some people even argue you are doing it wrong if you don't do this for any ng-model, but I find thinking wrapper object name is hard so I still use "dotless" one if I know the there is no child scope.

How do I reset a form including removing all validation errors?

I have an Angular form. The fields are validated using the ng-pattern attribute. I also have a reset button. I'm using the Ui.Utils Event Binder to handle the reset event like so:
<form name="searchForm" id="searchForm" ui-event="{reset: 'reset(searchForm)'}" ng-submit="search()">
<div>
<label>
Area Code
<input type="tel" name="areaCode" ng-model="areaCode" ng-pattern="/^([0-9]{3})?$/">
</label>
<div ng-messages="searchForm.areaCode.$error">
<div class="error" ng-message="pattern">The area code must be three digits</div>
</div>
</div>
<div>
<label>
Phone Number
<input type="tel" name="phoneNumber" ng-model="phoneNumber" ng-pattern="/^([0-9]{7})?$/">
</label>
<div ng-messages="searchForm.phoneNumber.$error">
<div class="error" ng-message="pattern">The phone number must be seven digits</div>
</div>
</div>
<br>
<div>
<button type="reset">Reset</button>
<button type="submit" ng-disabled="searchForm.$invalid">Search</button>
</div>
</form>
As you can see, when the form is reset it calls the reset method on the $scope. Here's what the entire controller looks like:
angular.module('app').controller('mainController', function($scope) {
$scope.resetCount = 0;
$scope.reset = function(form) {
form.$setPristine();
form.$setUntouched();
$scope.resetCount++;
};
$scope.search = function() {
alert('Searching');
};
});
I'm calling form.$setPristine() and form.$setUntouched, following the advice from another question here on Stack Overflow. The only reason I added the counter was to prove that the code is being called (which it is).
The problem is that even after reseting the form, the validation messages don't go away. You can see the full code on Plunker. Here's a screenshot showing that the errors don't go away:
I started with the comment from #Brett and built upon it. I actually have multiple forms and each form has many fields (more than just the two shown). So I wanted a general solution.
I noticed that the Angular form object has a property for each control (input, select, textarea, etc) as well as some other Angular properties. Each of the Angular properties, though, begins with a dollar sign ($). So I ended up doing this (including the comment for the benefit of other programmers):
$scope.reset = function(form) {
// Each control (input, select, textarea, etc) gets added as a property of the form.
// The form has other built-in properties as well. However it's easy to filter those out,
// because the Angular team has chosen to prefix each one with a dollar sign.
// So, we just avoid those properties that begin with a dollar sign.
let controlNames = Object.keys(form).filter(key => key.indexOf('$') !== 0);
// Set each control back to undefined. This is the only way to clear validation messages.
// Calling `form.$setPristine()` won't do it (even though you wish it would).
for (let name of controlNames) {
let control = form[name];
control.$setViewValue(undefined);
}
form.$setPristine();
form.$setUntouched();
};
$scope.search = {areaCode: xxxx, phoneNumber: yyyy}
Structure all models in your form in one place like above, so you can clear it like this:
$scope.search = angular.copy({});
After that you can just call this for reset the validation:
$scope.search_form.$setPristine();
$scope.search_form.$setUntouched();
$scope.search_form.$rollbackViewValue();
There doesn't seem to be an easy way to reset the $errors in angular. The best way would probably be to reload the current page to start with a new form. Alternatively you have to remove all $error manually with this script:
form.$setPristine(true);
form.$setUntouched(true);
// iterate over all from properties
angular.forEach(form, function(ctrl, name) {
// ignore angular fields and functions
if (name.indexOf('$') != 0) {
// iterate over all $errors for each field
angular.forEach(ctrl.$error, function(value, name) {
// reset validity
ctrl.$setValidity(name, null);
});
}
});
$scope.resetCount++;
You can add a validation flag and show or hide errors according to its value with ng-if or ng-show in your HTML. The form has a $valid flag you can send to your controller.
ng-if will remove or recreate the element to the DOM, while ng-show will add it but won't show it (depending on the flag value).
EDIT: As pointed by Michael, if form is disabled, the way I pointed won't work because the form is never submitted. Updated the code accordingly.
HTML
<form name="searchForm" id="searchForm" ui-event="{reset: 'reset(searchForm)'}" ng-submit="search()">
<div>
<label>
Area Code
<input type="tel" name="areaCode" ng-model="areaCode" ng-pattern="/^([0-9]{3})?$/">
</label>
<div ng-messages="searchForm.areaCode.$error">
<div class="error" ng-message="pattern" ng-if="searchForm.areaCode.$dirty">The area code must be three digits</div>
</div>
</div>
<div>
<label>
Phone Number
<input type="tel" name="phoneNumber" ng-model="phoneNumber" ng-pattern="/^([0-9]{7})?$/">
</label>
<div ng-messages="searchForm.phoneNumber.$error">
<div class="error" ng-message="pattern" ng-if="searchForm.phoneNumber.$dirty">The phone number must be seven digits</div>
</div>
</div>
<br>
<div>
<button type="reset">Reset</button>
<button type="submit" ng-disabled="searchForm.$invalid">Search</button>
</div>
</form>
JS
$scope.search = function() {
alert('Searching');
};
$scope.reset = function(form) {
form.$setPristine();
form.$setUntouched();
$scope.resetCount++;
};
Codepen with working solution: http://codepen.io/anon/pen/zGPZoB
It looks like I got to do the right behavior at reset. Unfortunately, using the standard reset failed. I also do not include the library ui-event. So my code is a little different from yours, but it does what you need.
<form name="searchForm" id="searchForm" ng-submit="search()">
pristine = {{searchForm.$pristine}} valid ={{searchForm.$valid}}
<div>
<label>
Area Code
<input type="tel" required name="areaCode" ng-model="obj.areaCode" ng-pattern="/^([0-9]{3})?$/" ng-model-options="{ allowInvalid: true }">
</label>
<div ng-messages="searchForm.areaCode.$error">
<div class="error" ng-message="pattern">The area code must be three digits</div>
<div class="error" ng-message="required">The area code is required</div>
</div>
</div>
<div>
<label>
Phone Number
<input type="tel" required name="phoneNumber" ng-model="obj.phoneNumber" ng-pattern="/^([0-9]{7})?$/" ng-model-options="{ allowInvalid: true }">
</label>
<div ng-messages="searchForm.phoneNumber.$error">
<div class="error" ng-message="pattern">The phone number must be seven digits</div>
<div class="error" ng-message="required">The phone number is required</div>
</div>
</div>
<br>
<div>
<button ng-click="reset(searchForm)" type="reset">Reset</button>
<button type="submit" ng-disabled="searchForm.$invalid">Search</button>
</div>
</form>
And JS:
$scope.resetCount = 0;
$scope.obj = {};
$scope.reset = function(form_) {
$scope.resetCount++;
$scope.obj = {};
form_.$setPristine();
form_.$setUntouched();
console.log($scope.resetCount);
};
$scope.search = function() {
alert('Searching');
};
Live example on jsfiddle.
Note the directive ng-model-options="{allowinvalid: true}". Use it necessarily, or until the entry field will not be valid, the model value is not recorded. Therefore, the reset will not operate.
P.S. Put value (areaCode, phoneNumber) on the object simplifies purification.
Following worked for me
let form = this.$scope.myForm;
let controlNames = Object.keys(form).filter(key => key.indexOf('$') !== 0);
for (let name of controlNames) {
let control = form [name];
control.$error = {};
}
In Short: to get rid of ng-messages errors you need to clear out the $error object for each form item.
further to #battmanz 's answer, but without using any ES6 syntax to support older browsers.
$scope.resetForm = function (form) {
try {
var controlNames = Object.keys(form).filter(function (key) { return key.indexOf('$') !== 0 });
console.log(controlNames);
for (var x = 0; x < controlNames.length; x++) {
form[controlNames[x]].$setViewValue(undefined);
}
form.$setPristine();
form.$setUntouched();
} catch (e) {
console.log('Error in Reset');
console.log(e);
}
};
I had the same problem and tried to do battmanz solution (accepted answer).
I'm pretty sure his answer is really good, but however for me it wasn't working.
I am using ng-model to bind data, and angular material library for the inputs and ng-message directives for error message , so maybe what I will say will be useful only for people using the same configuration.
I took a lot of look at the formController object in javascript, in fact there is a lot of $ angular function as battmanz noted, and there is in addition, your fields names, which are object with some functions in its fields.
So what is clearing your form ?
Usually I see a form as a json object, and all the fields are binded to a key of this json object.
//lets call here this json vm.form
vm.form = {};
//you should have something as ng-model = "vm.form.name" in your view
So at first to clear the form I just did callback of submiting form :
vm.form = {};
And as explained in this question, ng-messages won't disappear with that, that's really bad.
When I used battmanz solution as he wrote it, the messages didn't appear anymore, but the fields were not empty anymore after submiting, even if I wrote
vm.form = {};
And I found out it was normal, because using his solution actually remove the model binding from the form, because it sets all the fields to undefined.
So the text was still in the view because somehow there wan't any binding anymore and it decided to stay in the HTML.
So what did I do ?
Actually I just clear the field (setting the binding to {}), and used just
form.$setPristine();
form.$setUntouched();
Actually it seems logical, since the binding is still here, the values in the form are now empty, and angular ng-messages directive is triggering only if the form is not untouched, so I think it's normal after all.
Final (very simple) code is that :
function reset(form) {
form.$setPristine();
form.$setUntouched();
};
A big problem I encountered with that :
Only once, the callback seems to have fucked up somewhere, and somehow the fields weren't empty (it was like I didn't click on the submit button).
When I clicked again, the date sent was empty. That even more weird because my submit button is supposed to be disabled when a required field is not filled with the good pattern, and empty is certainly not a good one.
I don't know if my way of doing is the best or even correct, if you have any critic/suggestion or any though about the problem I encountered, please let me know, I always love to step up in angularJS.
Hope this will help someone and sorry for the bad english.
You can pass your loginForm object into the function ng-click="userCtrl.login(loginForm)
and in the function call
this.login = function (loginForm){
loginForm.$setPristine();
loginForm.$setUntouched();
}
So none of the answers were completely working for me. Esp, clearing the view value, so I combined all the answers clearing view value, clearing errors and clearing the selection with j query(provided the fields are input and name same as model name)
var modelNames = Object.keys($scope.form).filter(key => key.indexOf('$') !== 0);
modelNames.forEach(function(name){
var model = $scope.form[name];
model.$setViewValue(undefined);
jq('input[name='+name+']').val('');
angular.forEach(model.$error, function(value, name) {
// reset validity
model.$setValidity(name, null);
});
});
$scope.form.$setPristine();
$scope.form.$setUntouched();