How can I toggle form element using ng-show in directive? - javascript

Lets say I have a simple form like so:
<form ng-submit="methods.submit(formData)" ng-controller="diamondController" name="diamond" novalidate>
<help-block field="diamond.firstName"></help-block>
<input type="text" placeholder="" value="" id="firstName" name="firstName" ng-model="formData.firstName" class="" required>
</form>
I then want to replace the directive element with a pre-defined template, but parse in the diamond.firstName to the field attribute:
_core.directive('helpBlock', ['$compile', function($compile) {
return {
scope : {
field : '#'
},
require : '^form',
replace: true,
template : '<p ng-show="scope.field.$invalid && scope.field.$touched" class="help-block">Field is required.</p>',
link : function(scope, element, attrs, formCtrl) {
}
}
}]);
Do I have to do something to link 'field' to the actual form data? I can't figure this one out. I more or less just want to make it so that if the user clicks on the field and toggles out without filling in the required field, it shows the message.

You can bind your field using bidirectional databinding (=) and get rid of the scope prefix in your directive template:
app.directive('helpBlock', ['$compile', function($compile) {
return {
scope : {
field : '='
},
replace: true,
template : '<p ng-show="field.$invalid && field.$touched" class="help-block">Field is required.</p>'
}
}]);
See plnkr.

What They Do
ngShow and ngHide allow us to display or hide different elements. This helps when creating Angular apps since our single page applications will most likely have many moving parts that come and go as the state of our application changes.
The great part about these directives is that we don’t have to do any of the showing or hiding ourselves with CSS or JavaScript. It is all handled by good old Angular.
Usage
To use either ngShow or ngHide, just add the directive to the element you’d like to show or hide.
<!-- FOR BOOLEAN VALUES =============================== -->
<!-- for true values -->
<div ng-show="hello">this is a welcome message</div>
<!-- can also show if a value is false -->
<div ng-show="!hello">this is a goodbye message</div>
<!-- FOR EXPRESSIONS =============================== -->
<!-- show if the appState variable is a string of goodbye -->
<div ng-show="appState == 'goodbye'">this is a goodbye message</div>
<!-- FOR FUNCTIONS =============================== -->
<!-- use a function defined in your controller to evaluate if true or false -->
<div ng-hide="checkSomething()"></div>
Once we have that set in our markup, we can set the hello or goodbye variables a number of different ways. You could set it in your Angular controller and have the div show or hide when your app loads up.
All of the above can be used for ng-show or ng-hide. This will just hide something if the value, expression, or function returns true.
For more information Click Here

Related

Disabling submit button based on fields added with ng-bind-html

JSFiddle here: http://jsfiddle.net/c6tzj6Lf/4/
I am dynamically creating forms and buttons and want to disable the buttons if the required form inputs are not completed.
HTML:
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
JavaScript:
angular.module('choicesApp', ['ngSanitize'])
.controller('ChoicesCtrl', ['$scope', '$sce', function($scope, $sce) {
$scope.custom = "Required Input: <input required type='text'>";
$scope.trustCustom = function() {
return $sce.trustAsHtml($scope.custom);
};
$scope.buttons = [
{text:'Submit 1'},
{text:'Submit 2'}];
}]);
choicesForm.$invalid is false and does not change when entering text into the input field.
Solution:
I ended up using the angular-bind-html-compile directive from here: https://github.com/incuna/angular-bind-html-compile
Here is the relevant bit of working code:
<ng-form name="choicesForm">
<div ng-if="choices" bind-html-compile="choices"></div>
<button ng-click="submitForm()" ng-disabled="choicesForm.$invalid">
Submit
</button>
</ng-form>
And choices might be a snippit of HTML like this:
<div><strong>What is your sex?</strong></div>
<div>
<input type="radio" name="gender" ng-model="gender" value="female" required>
<label for="female"> Female</label><br>
<input type="radio" name="gender" ng-model="gender" value="male" required>
<label for="male"> Male</label>
</div>
The main problem is that ngBindHtml doesn't compile the html - it inserts the html as it is. You can even inspect the dynamic input and see that it doesn't have the ngModel's CSS classes (ng-pristine, ng-untouched, etc) which is a major red flag.
In your case, the form simply doesn't know that you've added another input or anything has changed for that matter. Its state ($pristine, $valid, etc) isn't determined by its HTML but by the registered NgModelControllers. These controllers are added automatically when an ngModel is linked.
For example this <input required type='text'> won't affect the form's validity, even if it's required, since it doesn't have ngModel assigned to it.
But this <div ng-model="myDiv" required></div> will affect it since it's required and has ngModel assigned to it.
The ngDisabled directive on your buttons works as expected since it depends on the form's $invalid property.
See this fiddle which showcases how ngModel registers its controller. Note that the html containing the dynamic input gets compiled after 750ms just to show how NgModelControllers can be added after FormController has been instantiated.
There are a few solutions in your case:
use a custom directive to bind and compile html - like this one
use ngInclude which does compile the html
use $compile to compile the newly added HTML but this is a bit tricky as you won't know exactly when to perform this action
This is an answer yet imcomplete because i cannot do the code at the moment.
I think your html will be included, not compiled. So the inputs are not bind to angular and are not part of the angular form object.
The only way i see is to use a directive that will compile the passed html and add it to your form. This may be quite tricky though, if you want to go on this way i suggest to edit your question to ask for the said directive.
However i'm not really familiar with $compile so i don't know if it'll work to just add $compile around $sce.trustAsHtml()
You can write a method as ng-disabled does not work with booleans, it works with 'checked' string instead:
So on your controller place a method :
$scope.buttonDisabled = function(invalid){
return invalid ? "checked" : "";
};
And on your view use it on angular expression :
<button ng-repeat="button in buttons" ng-disabled="buttonDisabled(choicesForm.$invalid)">
Here is a working fiddle
Working DEMO
This is the solution you are looking for. You need a custom directive. In my example I have used a directive named compile-template and incorporated it in div element.
<div ng-bind-html="trustCustom()" compile-template></div>
Directive Code:
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I found the directive in this fiddle.
I believe what is really happening though due to jsfiddle I'm unable to dissect the actual scopes being created here.
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
The first div is your top level scope, your form is the first child scope. Adding the div using a function creates the dynamically added input field as a child of the first child, a grandchild of the top level scope. Therefore your form is not aware of the elements you're adding dynamically causing only the static field to be required for valid form entry.
A better solution would be to use ng-inclue for additional form fields or if your form isn't to large then simply put them on the page or template you're using.

How to mix struts1 with angularjs to auto populate a field

I'm building a jsp page with struts1 (i cannot use struts2) where i have a field i'm auto populating with a value coming from the form, but the user is free to overwrite anything in that text field. I have also used AngularJS to be able to give options while the user types (google-like search) when they are overriding the value pre-populated by struts.
My problem is that the tags ng-model and uib-typeahead are not recognized by the html:text tag in struts. If i switch to a plain html input tag then the angular feature works but now i cannot pre-populate the field with struts.
script.js:
angular.module('plunker', ['ui.bootstrap','ngAnimate']).controller('TypeaheadCtrl', function($scope, $http) {
$scope.selected = undefined;
// Any function returning a promise object can be used to load values asynchronously
$scope.getLocation = function(val) {
return $http.get('http://maps.googleapis.com/maps/api/geocode/json', {
params: {
address: val,
sensor: false
}
}
).then(function(response){
return response.data.results.map(function(item){
return item.formatted_address;
});
});
};
});
mypage.html
<body ng-controller="TypeaheadCtrl">
<div class='container-fluid typeahead-demo' >
<div class="section">
<!-- OPTION 1: angular works and gives suggestions, but the field doesn't get pre-populated by struts-->
<input type="text" name="city" id="city" class="gui-input" ng-model="asyncSelected" uib-typeahead="address for getLocation($viewValue)" placeholder="Type city name">
<html:hidden property="city"/>
<!-- OPTION 2: auto-populates value but it can't compile with ng-model and typegead tags -->
<html:text property="city" styleClass="gui-input" styleId="cityStyle" />
</div>
</div>
Can someone please tell me what the best way or appropriate way of doing this is? how do i mix the functionality of Option 1 and Option 2?
Thanks in advance.
I already resolved it by setting the value of the field using jquery on load, but if someone has a better solution or better practice to do this, i'd like to know.

How to implement custom form & input directives in AngularJS (solving transcluded scope problems)?

What I tried to do the last days is something like that:
%myform(name='somename' ng-controller='whatever')
%myinput(ng-model='user.firstName' ...
controller has a user structure with firstName, lastname, ...
myform should just add some attributes to the <form>-tag, myinput should render a label, the input field and the errors when the somename-form-element is dirty and invalid. Pretty simple stuff.
As easy everything in AngularJS is, I had no chance. Had to move the ng-controller up to an extra div because nothing worked when the controller is defined in the myform tag (ng-click ignored, ...). Ugly but can live with that. No access to the scope in transcluded directives. Can be fixed with the link function and the append. Problem, the whole form validation stuff is not working when this fix is used. So I can have access to the form OR the scope.
What is the correct way to do this in AngularJS? I am really out of ideas and in despair after 4 days of trying and researching (learned the whole AngularJS in less than a day and not a single other problem).
Don't know if it makes sense to post ~ 30 different versions of trying to get this done. Maybe someone can provide a clean solution that is working and following the ideas behind the AngularJS framework (paypal beer thank you included).
Thank you very much in advance!
Anton
scope-fix-solutions:
http://angular-tips.com/blog/2014/03/transclusion-and-scopes/
Issue with transcoded directives: https://github.com/angular/angular.js/issues/5489
... there are thousands of problems about directives and transcoding, seems to be the most ugly part in Angular. Wanted to include more links to solutions I tried, but I am only allowed to post 2.
If somebody needs the solution (small example) - whole example on Plunker - provided by Sander Elias, many thanks!
HTML:
<body ng-controller='AppController as appVm'>
<h1>Hello angular {{appVm.version}}</h1>
<my-form name="test">
<div class="input-group">
<span class="input-group-addon">#</span>
<input type="text" class="form-control" ng-model='appVm.user' required placeholder="Username" name='username' ng-minlength=5>
</div>
<div ng-hide="test.$pristine">
<div ng-show="test.username.$error.required" class="alert alert-danger" role="alert">this is a required field</div>
<div ng-show="test.username.$error.minlength" class="alert alert-danger" role="alert">At least 5 chars</div>
</div>
<button class="btn btn-primary" ng-show='test.$touched || test.$valid'>submit</button>
</my-form>
</body>
JavaScript:
angular.element(document).ready( function() {
// generate module
myModule = angular.module( 'myApp',[]);
// define a simple controller and put the user's name into the scope
myModule.controller('SampleController', ['$scope', function ($scope) {
$scope.user = {
name: 'Hugo'
};
}]);
// make the form directive (just put the two attributes in the form...)
myModule.directive('myform', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<form ng-attr-name="{{name}}" autocomplete="off" novalidate=true>' +
'<fix-transclude></fix-transclude>' +
'</form>',
scope: {
name: '#'
},
link: function (scope, elm, attr, contrl, transclFn) {
scope.$parent[scope.name] = scope[scope.name];
// attach the parent scope (originating one!) to the transcluded content!
transclFn(scope.$parent,function (clone) {
elm.find('fix-transclude').replaceWith(clone);
});
}
}
});
// bootstrap AngularJS
angular.bootstrap(document, ['myApp']);
});

AngularJS form validation with isolated scope

I would like to use the built in form validation provided by AngularJS. However, within the form I am using custom directives that each have an isolate scope. Because of this the form element does not have access to the bound values.
Any idea how to fix this?
or, is it possible to use AngularJS validation without the use of a form?
The ng-minlength and ng-required directives are not triggering the form validation.
<div ng-app="myApp" ng-controller="myCtrl">
<form name="myForm" novalidate>
<do-something ng-model="variable"></do-something>
<h4 data-ng-if="myForm.myElement.$error.required">Please enter something</h4>
<h4 data-ng-if="myForm.myElement.$error.greaterThanOne">Please enter a value greater than 1</h4>
<h4 data-ng-if="myForm.myElement.$error.minLength">Please enter something longer than 1 digit</h4>
{{myForm.myElement.$error}}
</form>
</div>
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
});
app.directive('doSomething', function () {
return {
restrict: 'E',
require: '?ngModel',
scope: {
model: '=ngModel'
},
template:
'<div>' +
' <input name="myElement" ng-model="model" ng-required ng-minlength="1" />' +
'</div>'
}
});
Full Plunk can be found here: Here is a plunkr that demonstrates the problem: http://plnkr.co/edit/iWyvX2?p=preview
From my understanding, yes you have to use a form for validation.
The way in which I validate is to set up a directive like below
module.directive('ngDoSomething', function() {
restrict: 'A'
require: '?ngModel',
scope: {
model: '=ngModel'
}
link: function($scope, element, attrs, ngModel) {
$scope.$watch('model', function(val) {
ngModel.$setValidity('required', !!val);
ngModel.$setValidity('greaterThanOne', val > 1);
}
});
Then use the html
<form name="somethingForm">
<input name="somethingElement" data-ng-do-something data-ng-model="variable" />
<h4 data-ng-if="somethingForm.somethingElement.$error.required">Please enter something</h4>
<h4 data-ng-if="somethingForm.somethingElement.$error.greaterThanOne">Please enter a value greater than 1</h4>
</form>
I hope this helps
Ok Daniel, I was intrigued so I looked into it a bit further. The main difference between your code and #user3766487 is that you're using a directive element and injecting template. I believe this has caused a bit of ambiguity (you'll see that the directive itself and the inject input element both have the same name attribute). The linkage of the model doesn't appear to be quite working either.
I've changed your code to replace the template instead, which has made things a bit simpler. It appears to work:
http://plnkr.co/edit/eTSbjNe4KXW9IbUKtKuG
The ng-validators will work as expected with this 2 changes:
(1) Change the property name to "minlength" (instead of "minLength"):
<h4 data-ng-if="myForm.myElement.$error.minlength">
(2) Set ng-required to "true":
<input name="myElement" ... ng-required="true">
Setting minlength to "1" does not do anything because form validation does not check minlength wen the input is empty. If you set minlength to "5" and type one character in the input, you will see the message "Please enter something longer than 5 digit"
http://plnkr.co/edit/porkuq5JcKDU89s8g8ZT?p=preview
Your custom validator "greaterThanOne" is defined on the do-something directive. You can show its message by adding a name attribute such as myElementContainer:
<do-something name="myElementContainer" ng-model="myElement"></do-something>
<h4 data-ng-show="myForm.myElementContainer.$error.greaterThanOne">Please enter a value greater than 1</h4>
I would recommend to to define the logic in another directive as an attribute to the input element.
Also, using $validators is recommended over calling $setValidity:
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

AngularJS - Value attribute on an input text box is ignored when there is a ng-model used?

Using AngularJS if I set a simple input text box value to something like "bob" below. The value does not display if the ng-model attribute is added.
<input type="text"
id="rootFolder"
ng-model="rootFolders"
disabled="disabled"
value="Bob"
size="40"/>
Anyone know of a simple work around to default this input to something and keep the ng-model? I tried to use a ng-bind with the default value but that seems not to work either.
That's desired behavior, you should define the model in the controller, not in the view.
<div ng-controller="Main">
<input type="text" ng-model="rootFolders">
</div>
function Main($scope) {
$scope.rootFolders = 'bob';
}
Vojta described the "Angular way", but if you really need to make this work, #urbanek recently posted a workaround using ng-init:
<input type="text" ng-model="rootFolders" ng-init="rootFolders='Bob'" value="Bob">
https://groups.google.com/d/msg/angular/Hn3eztNHFXw/wk3HyOl9fhcJ
Overriding the input directive does seem to do the job. I made some minor alterations to Dan Hunsaker's code:
Added a check for ngModel before trying to use $parse().assign() on fields without a ngModel attributes.
Corrected the assign() function param order.
app.directive('input', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if (attrs.ngModel && attrs.value) {
$parse(attrs.ngModel).assign(scope, attrs.value);
}
}
};
});
The Angular way
The correct Angular way to do this is to write a single page app, AJAX in the form template, then populate it dynamically from the model. The model is not populated from the form by default because the model is the single source of truth. Instead Angular will go the other way and try to populate the form from the model.
If however, you don't have time to start over from scratch
If you have an app written, this might involve some fairly hefty architectural changes. If you're trying to use Angular to enhance an existing form, rather than constructing an entire single page app from scratch, you can pull the value from the form and store it in the scope at link time using a directive. Angular will then bind the value in the scope back to the form and keep it in sync.
Using a directive
You can use a relatively simple directive to pull the value from the form and load it in to the current scope. Here I've defined an initFromForm directive.
var myApp = angular.module("myApp", ['initFromForm']);
angular.module('initFromForm', [])
.directive("initFromForm", function ($parse) {
return {
link: function (scope, element, attrs) {
var attr = attrs.initFromForm || attrs.ngModel || element.attrs('name'),
val = attrs.value;
if (attrs.type === "number") {val = parseInt(val)}
$parse(attr).assign(scope, val);
}
};
});
You can see I've defined a couple of fallbacks to get a model name. You can use this directive in conjunction with the ngModel directive, or bind to something other than $scope if you prefer.
Use it like this:
<input name="test" ng-model="toaster.test" value="hello" init-from-form />
{{toaster.test}}
Note this will also work with textareas, and select dropdowns.
<textarea name="test" ng-model="toaster.test" init-from-form>hello</textarea>
{{toaster.test}}
Update: My original answer involved having the controller contain DOM-aware code, which breaks Angular conventions in favor of HTML. #dmackerman mentioned directives in a comment on my answer, and I completely missed that until just now. With that input, here's the right way to do this without breaking Angular or HTML conventions:
There's also a way to get both - grab the value of the element and use that to update the model in a directive:
<div ng-controller="Main">
<input type="text" id="rootFolder" ng-model="rootFolders" disabled="disabled" value="Bob" size="40" />
</div>
and then:
app.directive('input', ['$parse', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if(attrs.value) {
$parse(attrs.ngModel).assign(scope, attrs.value);
}
}
};
}]);
You can of course modify the above directive to do more with the value attribute before setting the model to its value, including using $parse(attrs.value, scope) to treat the value attribute as an Angular expression (though I'd probably use a different [custom] attribute for that, personally, so the standard HTML attributes are consistently treated as constants).
Also, there is a similar question over at Making data templated in available to ng-model which may also be of interest.
If you use AngularJs ngModel directive, remember that the value of value attribute does not bind on ngModel field.You have to init it by yourself and the best way to do it,is
<input type="text"
id="rootFolder"
ng-init="rootFolders = 'Bob'"
ng-model="rootFolders"
disabled="disabled"
value="Bob"
size="40"/>
This is a slight modification to the earlier answers...
There is no need for $parse
angular.directive('input', [function () {
'use strict';
var directiveDefinitionObject = {
restrict: 'E',
require: '?ngModel',
link: function postLink(scope, iElement, iAttrs, ngModelController) {
if (iAttrs.value && ngModelController) {
ngModelController.$setViewValue(iAttrs.value);
}
}
};
return directiveDefinitionObject;
}]);
Hi you can try below methods with initialize of model.
Here you can initialize ng-model of textbox two way
- With use of ng-init
- With use of $scope in js
<!doctype html>
<html >
<head>
<title>Angular js initalize with ng-init and scope</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
</head>
<body ng-app="app" >
<h3>Initialize value with ng-init</h3>
<!-- Initlialize model values with ng-init -->
<div ng-init="user={fullname:'Bhaskar Bhatt',email:'bhatt.bhaskar88#gmail.com',address:'Ahmedabad'};">
Name : <input type="text" ng-model="user.fullname" /><br/>
Email : <input type="text" ng-model="user.email" /><br/>
Address:<input type="text" ng-model="user.address" /><br/>
</div>
<!-- initialize with js controller scope -->
<h3>Initialize with js controller</h3>
<div ng-controller="alpha">
Age:<input type="text" name="age" ng-model="user.age" /><br/>
Experience : <input type="text" name="experience" ng-model="user.exp" /><br/>
Skills : <input type="text" name="skills" ng-model="user.skills" /><br/>
</div>
</body>
<script type="text/javascript">
angular.module("app",[])
.controller("alpha",function($scope){
$scope.user={};
$scope.user.age=27;
$scope.user.exp="4+ years";
$scope.user.skills="Php,javascript,Jquery,Ajax,Mysql";
});
</script>
</html>
The issue is that you have to set the ng-model to the parent element to where you want to set the ng-value/value .
As mentioned by Angular:
It is mainly used on input[radio] and option elements, so that when the element is selected, the ngModel of that element (or its select parent element) is set to the bound value.
Eg:This is an executed code :
<div class="col-xs-12 select-checkbox" >
<label style="width: 18em;" ng-model="vm.settingsObj.MarketPeers">
<input name="radioClick" type="radio" ng-click="vm.setPeerGrp('market');"
ng-value="vm.settingsObj.MarketPeers"
style="position:absolute;margin-left: 9px;">
<div style="margin-left: 35px;color: #717171e8;border-bottom: 0.5px solid #e2e2e2;padding-bottom: 2%;">Hello World</div>
</label>
</div>
Note: In this above case I alreday had the JSON response to the ng-model and the value, I am just adding another property to the JS object as "MarketPeers". So the model and value may depend according to the need, but I think this process will help, to have both ng-model and value but not having them on the same element.
I had similar issue. I was not able to use value="something" to display and edit.
I had to use the below command inside my <input>along withe ng model being declared.
[(ngModel)]=userDataToPass.pinCode
Where I have the list of data in the object userDataToPass and the item that I need to display and edit is pinCode.
For the same , I referred to this YouTube video

Categories

Resources