How to make Angular bind blank inputs to a model? - angularjs

Here is a simple Angular example:
<!DOCTYPE html>
<html ng-app="GenericFormApp">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
</head>
<body ng-controller="GenericFormCtrl as ctrl">
<div>
Model: {{ctrl.model}}
</div>
<div>
<input ng-model="ctrl.model" />
</div>
<div>
<input type="button" value="Alert model" ng-click="ctrl.showModel();" />
</div>
<script>
angular.module("GenericFormApp", [])
.controller("GenericFormCtrl", [function () {
this.showModel = function () { alert(this.model); };
}])
</script>
</body>
</html>
The above shows how to bind an input to a model, a fundamental feature of Angular.
It also allows the user to pop up a modal dialog with the contents of the input. This works fine except when the input is left blank.
In that case, it displays "undefined".
I could, of course, simply write a line of code that sets the initial value of the model to a blank string, but this is not particularly practical because in my real application, there are many inputs, and the user may leave any number of them blank.
In short, I want to know how to make it so that Angular knows that a blank input should contain a blank string in the model.

I would go with custom directive to extend default input directive behaviour. So in case if input has a model this directive would check if this model is undefined and if so assign it an empty string value.
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
<div ng-app="GenericFormApp" ng-controller="GenericFormCtrl as ctrl">
<input ng-model="ctrl.model" /> {{ctrl.model}}<br>
<input type="button" value="Alert model" ng-click="ctrl.showModel();" />
<script>
angular.module("GenericFormApp", [])
.controller("GenericFormCtrl", [function () {
this.showModel = function () { alert(this.model); };
}])
.directive("input", function($parse) {
return {
link: function(scope, element, attr, ngModelController) {
if (attr.ngModel) {
var model = $parse(attr.ngModel);
if (typeof model(scope) === 'undefined') {
model.assign(scope, '');
}
}
}
};
});
</script>
</div>

I igree with #Claies, but, if you need this for some specific attributes, you can use ng-init:
<input type="text" ng-init="ctrl.model = ctrl.model || ''" ng-model="ctrl.model"/>
or create a specific directive, like 'auto-init' or similar, not directly on input element.

Related

AngularJS hide any parent div using ng-click in a child element

I already know about ng-if and ng-show methods of showing/hiding DOM elements. In this case, however, I have about 100 div elements, each with multiple child span elements, and whenever a span is clicked, I want the parent div to hide.
Example:
<div>Display text
<span ng-click="hideThisDiv(this)">Option1</span>
<span ng-click="hideThisDiv(this)">Option2</span>
<span ng-click="hideThisDiv(this)">Option3</span>
</div>
In the function, I want to be able to do something like:
$scope.hideThisDiv = function(element){
element.$parent.$id.visible = false;
}
Using console.log(element.$parent) in this function shows, however, that there isn't a simple way to access a "visible" property of this div element. At least, not that I can see so far.
This seems like a simple concept, I'm just lacking the proper syntax or access method.
Try below code it works
var app = angular.module('myApp', []);
app.controller('MainCtrl', function ($scope) {
$scope.hideParent = function (event) {
var pEle = event.currentTarget.parentElement;
pEle.style.visibility = "hidden";
}
});
<!DOCTYPE html>
<html ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-controller="MainCtrl">
<div>
This is parent div click below to hide <br />
<span ng-click="hideParent($event)">Click here to hide</span> <br />
<span ng-click="hideParent($event)">or Here</span><br />
<span ng-click="hideParent($event)">or Here</span>
</div>
</body>
</html>
If you prefer to do this with jquery then use the jqLite approach with angular.element like this:
$scope.hideThisDiv = function(el) {
angular.element(el.target).parent().addClass('hidden');
};
Then pass in the event like this:
<span ng-click="hideThisDiv($event)">Option1</span>
The add this to your css
.hidden {
display:none
}
Solution:
The better approach is to create a custom directive and hide the parent element using jqLite.
var app = angular.module('app', []);
app.directive('hideParentOnClick', function () {
return {
link: function (scope, element) {
element.on('click', function () {
element.parent().css({display: 'none'});
});
}
}
});
And in your HTML:
<div>
Display text
<span hide-parent-on-click>Option1</span>
<span hide-parent-on-click>Option2</span>
<span hide-parent-on-click>Option3</span>
</div>
Plunker Example
Advantages:
You can combine this directive with the aforementioned ng-click because the last one is not utilized in this method and can be freely used for any other purpose.
Directives are intended for DOM manipulations, not controllers. Read more here.
Better overall modularity.

When does "$viewValue" become "NOT undefined"?

Here is my example to count value length in the input text:
let app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.submit = function ($event, form) {
$event.preventDefault();
alert(form.myinput.$viewValue.length)
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form id="myForm" ng-model="myForm" name="myForm" novalidate ng-app="myApp" ng-controller="myController">
<input ng-model="myinput" name="myinput" />
Submit
</form>
The problem: If the value in the input is null or empty (the input contained nothing before), it would throw this error when clicking on submit:
TypeError: Cannot read property 'length' of undefined
at a.$$childScopeClass.$$childScopeClass.$scope.submit
Then, I've tried to type something in the input, delete it and click on submit again. It should work.
My question: for input[type=text], is there nothing like default value with property $viewValue?
I mean: if the value is null or empty, form.myinput.$viewValue should be ''. So, the length must be 0.
Try this :
It will check first for null and empty value of the text box and then perform operation according to that.
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.submit = function () {
if($scope.myinput != null && $scope.myinput.length > 0) {
alert($scope.myinput.length);
} else {
alert("Please enter the text");
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form id="myForm" name="myForm" novalidate ng-app="myApp" ng-controller="myController">
<input ng-model="myinput" name="myinput" />
<button ng-click="submit()">Submit</button>
</form>
You need to access it via the scope. $scope.form.myinput.$viewValue.length
That being said I do not believe that controllers should know about form as forms are a view concept. Anything to do with the form variable should not make their way into your controllers. I am a big fan of not passing the $scope into your controllers at all and using the controller as syntax.
Here is how I would do it. This will only work with Angular 1.3 or greater as 1.2 doesn't support controller as.
let app = angular.module('myApp', []);
app.controller('myController', function () {
var vm = this;
vm.submit = function () {
alert(vm.myinput);
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<form id="myForm" name="myForm" novalidate ng-app="myApp" ng-controller="myController as vm">
<input ng-model="vm.myinput" name="myinput" />
<button ng-click="vm.submit()">Submit</button>
</form>

How do I choose which <input> gets focus in ng-repeat

I'm just starting to get to grips with angular and I am trying to do something that I think should be pretty simple, but I can't find anyone who has posted with exactly the same scenario. I have a collection which is initiated with three objects and I am using ng-repeat to generate a set of input fields for each of these objects. When the SPA is initialised I want the first input field to have focus: I can do with with autofocus if necessary. When the user TABs off the last input field I add another object to the collection using ng-blur="addRecord($index)". When the DOM is refreshed I want the first field in the new object to have focus. The difference between my effort and all the examples I can find online is that all the examples initiate the addition using a button and an ng-click event.
Because the DOM element is going to be created on the fly, I think I need a custom directive with a $timeout but this seems like a lot of work for what should be a fairly standard requirement. I am using 1.3.x Can anyone show me the basics of how to write the directive or point me at a library that already exists that will do what I want. My current code is set out below.
HTML
<body ng-app="myApp">
<div ng-controller="playerController">
<ul>
<li ng-repeat="player in players">
<input type="text" placeholder="FirstName" ng-model="player.firstName"></input>
<input type="text" placeholder="NicktName" ng-model="player.nickName"></input>
<input type="text" placeholder="SurnameName" ng-model="player.lastName" ng-blur="addNew($index)"></input>
{{player.firstName}} "{{player.nickName}}" {{player.lastName}}
</li>
</ul>
</div>
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="myApp.js"></script>
</body>
myApp.js
var myApp = angular.module('myApp',[]);
myApp.controller('playerController',function($scope){
$scope.players = [
{
"firstName":"Aaron",
"lastName":"Reese",
"nickName":"Star Wars",
"givemefocus": "true"
},
{
"firstName":"Ian",
"lastName":"Soinne",
"nickName":"Dominian",
"givemefocus": "false"
},
{
"firstName":"Aaron",
"lastName":"Bailey",
"nickName":"Fernando",
"givemefocus": "false"
}
];
$scope.addNew = function($index){
if($index == (players.length -1 )){
$scope.newPlayer = {
"firstName":"",
"lastName":"",
"nickName":"",
"givemefocus": "true"
};
$scope.players.push($scope.newPlayer);
}
}
});
app.directive('takefocus', function($timeout) {
return function(scope, element, attrs) {
scope.$watch(attrs.takefocus, function(value) {
if (value) {
$timeout(function() { element.focus(); });
}
});
};
});
In html:
<li ng-repeat="player in players">
<input type="text" placeholder="FirstName" ng-model="player.firstName" takefocus="player.givemefocus"></input>
Add Id to first input <input id=input{{$index}}../> to find this input later in onBlur function.
<li ng-repeat="player in players">
<input id="input{{$index}}" type="text" placeholder="FirstName" ng-model="player.firstName"></input>
<input type="text" placeholder="NicktName" ng-model="player.nickName"></input>
<input type="text" placeholder="SurnameName" ng-model="player.lastName" ng-blur="addNew($index)"></input>
{{player.firstName}} "{{player.nickName}}" {{player.lastName}}
</li>
Add $timeout to controller. In function addNew use $timeout with zero delay to wait to the end of DOM rendering. Then input can be found by getElementById in $timeout function.
myApp.controller('playerController',function($scope, $timeout)
{
$scope.addNew = function($index){
if($index == (players.length -1 )){
$scope.newPlayer = {
"firstName":"",
"lastName":"",
"nickName":"",
"givemefocus": "true"
};
$scope.players.push($scope.newPlayer);
$timeout(function ()
{
document.getElementById("input" + ($index + 1)).focus();
});
}
}
});

Using $compile in a directive inside an ng-if

I am trying to add validation dynamically to an input, with the intent of having the controller store each of the input's validation types and error messages. It is working, but not when the form is in an ng-if that is only set to true once an async call returns.
Here is a trimmed down example of it not working:
http://plnkr.co/edit/aeZJXqtwVs85nPE1Pn2T?p=preview
If you remove the ng-if from the containing div, validation starts to work and the input is populated with the data that is set in the async call. With the ng-if, text isn't populated and validation never fires.
<body ng-controller="MainCtrl">
<div ng-if="name">
<form name="demoForm">
<input type="text"
name="password"
ng-model='name'
add-min-length
ng-maxlength='15'
required/>
<div ng-messages='demoForm.password.$error'>
<div ng-message='required'>required</div>
<div ng-message='minlength'>min length</div>
<div ng-message='maxlength'>max length</div>
</div>
</form>
</div>
</body>
Remove ng-if="name" and it starts to work.
app.controller('MainCtrl', function($scope, $timeout) {
$timeout(function(){
$scope.name = 'Thing';
}, 1000);
});
app.directive('addMinLength', function($compile){
return{
priority: 1001,
terminal: true,
compile: function(el){
el.removeAttr('add-min-length')
el.attr('ng-minlength', '5');
var fn = $compile(el);
return function(scope){
fn(scope);
}
}
}
});
Does anyone have an explanation of why this might be?
I have read https://groups.google.com/forum/#!searchin/angular/ng-if$20directive/angular/Vjo4HZ2bW1A/vKWf-m6BKMkJ
and it seems the issue might be similar, but not quite what I need.
Thanks for any help

How can I use Angular to output dynamic form fields?

I want to render a form, based on a dynamic field configuration:
$scope.fields = [
{ title: 'Label 1', type: 'text', value: 'value1'},
{ title: 'Label 2', type: 'textarea', value: 'value2'}
];
This should output something that behaves like:
<div>
<label>{{field.title}}<br />
<input type="text" ng-model="field.value"/>
</label>
</div>
<div>
<label>{{field.title}}<br />
<textarea ng-model="field.value" rows="5" cols="50"></textarea>
</label>
</div>
The simple implementation would be to use if statements to render the templates for each field type. However, as Angular doesn't support if statements, I'm lead to the direction of directives. My problem is understanding how the data binding works. The documentation for directives is a bit dense and theoretical.
I've mocked up a bare bones example of what I try to do here: http://jsfiddle.net/gunnarlium/aj8G3/4/
The problem is that the form fields aren't bound to the model, so the $scope.fields in submit() isn't updated. I suspect the content of my directive function is quite wrong ... :)
Going forward, I need to also support other field types, like radio buttons, check boxes, selects, etc.
The first problem you are running into regardless of the directive you are trying to create is using ng-repeat within a form with form elements. It can be tricky do to how ng-repeat creates a new scope.
This directive creates new scope.
I recommend also instead of using element.html that you use ngSwitch instead in a partial template.
<div class="form-row" data-ng-switch on="field.type">
<div data-ng-switch-when="text">
{{ field.title }}: <input type="text" data-ng-model="field.value" />
</div>
<div data-ng-switch-when="textarea">
{{ field.title }}: <textarea data-ng-model="field.value"></textarea>
</div>
</div>
This still leaves you with the problem of modifying form elements in child scope due to ng-repeat and for that I suggest using the ngChange method on each element to set the value when an item has changed. This is one of the few items that I don't think AngularJS handles very well at this time.
You might consider Metawidget for this. It uses JSON schema, but is otherwise very close to your use case. Complete sample:
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
<script type="text/javascript">
angular.module( 'myApp', [ 'metawidget' ] )
.controller( 'myController', function( $scope ) {
$scope.metawidgetConfig = {
inspector: function() {
return {
properties: {
label1: {
type: 'string'
},
label2: {
type: 'string',
large: true
}
}
}
}
}
$scope.saveTo = {
label1: 'value1',
label2: 'value2'
}
$scope.save = function() {
console.log( $scope.saveTo );
}
} );
</script>
</head>
<body ng-controller="myController">
<metawidget ng-model="saveTo" config="metawidgetConfig">
</metawidget>
<button ng-click="save()">Save</button>
</body>
</html>
The type attribute can be changed when the element is out of DOM, so why not a small directive which removes it from DOM, changes it type and then add back to the same place?
The $watch is optional, as the objective can be change it dynamically once and not keep changing it.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.rangeType = 'range';
$scope.newType = 'date'
});
app.directive('dynamicInput', function(){
return {
restrict: "A",
link: linkFunction
};
function linkFunction($scope, $element, $attrs){
if($attrs.watch){
$scope.$watch(function(){ return $attrs.dynamicInput; }, function(newValue){
changeType(newValue);
})
}
else
changeType($attrs.dynamicInput);
function changeType(type){
var prev = $element[0].previousSibling;
var parent = $element.parent();
$element.remove().attr('type', type);
if(prev)
angular.element(prev).after($element);
else
parent.append($element);
}
}
});
span {
font-size: .7em;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="MainCtrl">
<h2>Watching Type Change</h2>
Enter Type: <input ng-model="newType" /><br/>
Using Type (with siblings): <span>Before</span><input dynamic-input="{{newType}}" watch="true" /><span>After</span><Br>
Using Type (without siblings): <div><input dynamic-input="{{newType}}" watch="true" /></div>
<br/><br/><br/>
<h2>Without Watch</h3>
Checkbox: <input dynamic-input="checkbox" /><br />
Password: <input dynamic-input="{{ 'password' }}" value="password"/><br />
Radio: <input dynamic-input="radio" /><br/>
Range: <input dynamic-input="{{ rangeType }}" />
</div>
Tested in latest Chrome and IE11.

Resources