Im new in Angularjs
and I would like to do something like the following in directive
<!--get numb1&numb2 from user input-->
<div>
<input ng-model="numb1" type=number/>
</div>
<div>
<input ng-model="numb2" type=number/>
</div>
<!--result display on the following input box, not allow to edit-->
<div>
<input ng-model="result" formula="some formula here, can be anything" readonly/>
</div>
numb1 & numb2 can be change anytime, use $watch instead on ngChange.
Can anyone guide me on this?
You can achieve your requirement with the following code snippet.
The HTML
<div ng-app="demo">
<div ng-controller="DefaultController as vm">
<inputs formula="vm.formula"></inputs>
</div>
</div>
<script type="text/template" id="inputsTemplate">
<div>
<input type="number" ng-model="vm.a"/>
</div>
<div>
<input type="number" ng-model="vm.b"/>
</div>
<div>
<input type="number" ng-model="vm.result" readonly/>
</div>
</script>
The AngularJS code
angular
.module('demo', [])
.controller('DefaultController', DefaultController)
.controller('InputsController', InputsController)
.directive('inputs', inputs);
function DefaultController() {
var vm = this;
vm.formula = 'a + b';
}
function inputs() {
var directive = {
restrict: 'E',
scope: {
formula: '='
},
template: function () {
return angular.element(document.querySelector('#inputsTemplate')).html();
},
controller: InputsController,
controllerAs: 'vm',
bindToController: true
};
return directive;
}
InputsController.$inject = ['$scope', '$parse'];
function InputsController($scope, $parse) {
var vm = this;
var expressionFunc = $parse(vm.formula);
$scope.$watchGroup(['vm.a', 'vm.b'], function (newValue, oldValue, scope) {
if (angular.isDefined(vm.a) && angular.isDefined(vm.b)) {
var result = expressionFunc(vm);
vm.result = result;
}
});
}
The jsfiddle link here
The coding style is followed from angular-styleguide by John Papa and from Pro AngularJS by Adam Freeman
Explanation:
There is a main controller which is having a formula set on its scope such as a + b and which is passed to a directive called inputs through an attribute called formula, this an element directive meaning that it is restricted to be applied only as an individual html element.
The inputs directive gets its template from a script tag having html content
by using the template property of the directive definition. The inputs directive has its own controller bound using the controller aliasing syntax.
The inputs directive has three objects on its controller scope they're a, b and result and the magic happens in the InputsController using the $parse service which returns a function when an expression is parsed which can indeed be evaluated on an object to execute that expression and get the result i.e.
var expressionFunc = $parse(vm.formula);
var result = expressionFunc(vm);
vm.result = result;
Also as we need to watch both a and b objects on the scope, we can use the $watchGroup function on the $scope object to handle the change events and update the view accordingly.
Note: we are only parsing once var expressionFunc = $parse(vm.formula); and executing the expression many times var result = expressionFunc(vm); this is an important factor and should be considered for quality of the code and for performance.
You are in the same scope, so, you can use ng-value in order to set a new value given numb1 and numb2. For example, you formula is numb1 + numb2, just try something like this:
<input ng-model="result" ng-value="{{numb1 + numb2}}" readonly/>
Related
I am trying to make a directive that is basically a text box with a max length counter on it. My directive is below. Basically a text box that will tell the user that they only have x number of characters left.
angular.module('InputApp', []);
angular.module('InputApp').directive('textAreaCounter', function () {
return {
templateUrl: '/Content/Directives/Inputs/TextAreaCounter.html',
restrict: 'AE',
multiElement: true,
scope: {
text: '='
},
link: function (scope, elem, attrs) {
if (scope.text == undefined || scope.text == '') {
scope.CharactersLeft = attrs.totalCharacters;
} else {
scope.CharactersLeft = attrs.totalCharacters - scope.text.length;
}
scope.TextValueChanged = function () {
scope.CharactersLeft = attrs.totalCharacters - scope.text.length;
}
}
}
});
The template html is:
<div class="row">
<div class="col-md-12">
<textarea ng-model="text" ng-change="TextValueChanged()" autogrow rows="5"></textarea>
</div>
</div>
<div class="row">
<div class="col-md-12 top-left smallText">
You have {{CharactersLeft}}.
</div>
</div>
And I use the directive like this.
<text-Area-Counter text="WholeDeletionText" total-Characters="250"></text-Area-Counter>
The issue that I am having is that the value 'WholeDeletionText' is not being updated by the directive.
My expectation is that the scope.WholeDeletionText in the parent would update with the text that was written in the textarea in the directive. At least my understanding of the isolated scope having the '=' sign is that they share it with the parent. What am I doing wrong or is there a better way to do it?
The problem occurs because you have not WholeDeletionText variable in the application scope.
To fix it you need:
1) create controller and initialize WholeDeletionText variable
.controller("MyController", function ($scope) {
$scope.WholeDeletionText = '123';
});
2) add ng-controller="MyController" directive in your main html file.
<body ng-app="InputApp" ng-controller="MyController">
<div>
<text-Area-Counter text="WholeDeletionText" total-Characters="250"></text-Area-Counter>
</div>
</body>
Full code see in Plunk.
Exactly, adding to the #Roman's point, when you use '=' in declaring the directive's scope variable it should be declared the the parent controller as well, otherwise you can use '#'.
Is that possible to parsing function to angularjs directive that return a templateUrl? In my case, I have this directive
.directive('forumForm', function(){
return {
restrict : 'C',
scope : {
data : '=forum',
},
templateUrl : '/templates/forum_form.tpl.html'
}
});
This is my tempalteUrl
<input type="text" ng-model="data.Title" name="nameF" class="form-control" required="" ng-minlength="20" ng-maxlength="100">
<input type="" class="tagsinput" ng-model="data.tagIn" />
<button type="button" ng-click="fn(data)">Submit</button>
And, I call that via class like this
<div class="forumForm" forum="forum"></div>
Last, my controller have a function called fn
$scope.fn = function((){
alert('text')
})
You can see that I parsing a forum variable to my templateUrl via directive. My problem is, Is that possible to parsing a function in that directive? So if I create
<div class="forumForm" forum="forum" fn="action(forum)"></div>
And if I click the button (In my templateUrl), It's call a function that I have written in controller. Is that possible?
Yes, you can use & binding for this:
The & binding allows a directive to trigger evaluation of an
expression in the context of the original scope, at a specific time.
Any legal expression is allowed, including an expression which
contains a function call. Because of this, & bindings are ideal for
binding callback functions to directive behaviors.
Example:
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
var ctrl = this;
ctrl.forum = {}
ctrl.log = log;
function log(data){
console.log(data);
};
}])
.directive('forumForm', [function () {
var forumForm = {
restrict : 'EC',
scope : {
data : '=forum',
fn: '&'
},
templateUrl : 'forum_form.tpl.html'
}
return forumForm;
}]);
<script src="//code.angularjs.org/1.6.2/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl as $ctrl">
<forum-form forum="$ctrl.forum" fn="$ctrl.log(data)"></forum-form>
</div>
<script type="text/ng-template" id="forum_form.tpl.html">
<input type="text" ng-model="data.title" />
<input type="" class="tagsinput" ng-model="data.tagIn" />
<button type="button" ng-click="fn({data: data})">Submit</button>
</script>
</div>
how to create a directive for disable all elements into div element ?
something like this :
<div div-disabled div-disabled-condition="state=='Stack'||state=='Over'||state=='Flow'">
<input type="text"/>
<input type="url"/>
<div>
<input type="text"/>
<input type="url"/>
</div>
<div>
Is it possible? I have no idea .
angular
.module('uiRouterApp.ctrl.add', ['uiRouterApp.ctrl.customDirective'])
.controller('addCtrl', [
'$scope',
'$location',
'$stateParams',
'$state',
function ($scope, $location, $stateParams, $state) {
$scope.state = {};
}
]).directive('divDisabled', function () {
return {
scope: {
divDisabledCondition: '#'
},
link: function (scope, element, attrs) {
}
};
});
Update :
please see this :
<div class="col-sm-12 ng-isolate-scope" selected-object="SelectedAutoComplete" local-data="requirements.Item1" search-fields="NameFa,NameEn" title-field="NameFa" minlength="2" field-required="true" image-field="ImageUrl" disable-auto-compelete="response.State=='Success'||response.State=='Error'||response.State=='Warning'">
<div class="angucomplete-holder">
<input id="_value" ng-model="searchStr" type="text" placeholder="select" class="form-control ng-dirty" ng-focus="resetHideResults()" ng-blur="hideResults()" autocapitalize="off" autocorrect="off" autocomplete="off" ng-change="inputChangeHandler(searchStr)" ng-disabled="response.State=='Success'||response.State=='Error'||response.State=='Warning'" style="">
<!-- ngIf: showDropdown -->
</div>
</div>
directive :
.directive('contentsDisabled', function() {
return {
compile: function(tElem, tAttrs) {
var inputs = tElem.find('input');
for (var i = 0; i < inputs.length; i++) {
inputs.attr('ng-disabled', tAttrs['disableAutoCompelete']);
}
}
}
})
why When the state is 'Success' or 'Error' or 'Warning' Input not disabled ?
You can create a directive that alters its content during compile time by adding the condition. Something along these lines (untested):
module.directive('contentsDisabled', function() {
return {
compile: function(tElem, tAttrs) {
var inputs = tElem.find('input');
inputs.attr('ng-disabled', tAttrs['contentsDisabled']);
}
};
});
See a JSFiddle here: http://jsfiddle.net/HB7LU/6380/
This has the drawback that you just copy the expression from contents-disabled into ng-disabled attributes of any inputs - if somebody uses a directive that in turn creates <input> elements, you won't pick them up.
It'd be less fragile to get hold of the FormController instance and iterate through all its controls, but sadly AngularJS doesn't expose the controls in a form. Maybe file a feature request?
You also can use a tag fieldset :
<form>
<fieldset ng-disable="foo">
<input name="the_one"/>
<input name="the_second"/>
</fieldset>
<input name="the_thrid"/>
</form>
With this way, when the variable foo is TRUE, inputs "the_one" and "the_second" will be disabled.
Why don't you use ng-disabled on your required expression on each input?
https://docs.angularjs.org/api/ng/directive/ngDisabled
If you truly do want a grouping directive, use the compile function of the directive to insert the ng-disabled attribute on each child. Or use a paren't child directive to signify which children to apply the ng-disabled to.
There is a new option to control enable/disable input field for angucomplete-alt.
http://ghiden.github.io/angucomplete-alt/#example13
I'm trying to set an input like
<form name="myForm">
<input name="{{ name }}"/>
</form>
It works in the dom. I see
<input name="whatever_name_is_set_to"/>
However in my ngForm I have
$scope.myForm: {
{{ name }}: { }
}
Doh'
Why am I doing this? I'm trying to create a directive so that I can build my forms programmatically. Then I can do something like
<div my-field
name="credits"
field="course.credits"
field-options="courseOptions.credits"
title="Credits"
></div>
Plunker
Updated 2017-03-23:
For AngularJS >= 1.3 interpolation is now supported in input names.
Original answer from 2014-06-05 (correct for AngularJS <= 1.2):
I just answered a similar question yesterday about dynamically named form elements. In short, no, you can't use interpolation to dynamically name your form fields - the interpolation string will end up in the field name as you have seen.
In your case you're probably going to need to look into dynamically compiling the input HTML inside your myField directive.
Here is a simplified example using $compile to dynamically generate form elements: http://jsfiddle.net/Sly_cardinal/XKYJ3/
HTML:
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<div my-field
name="courseName"
field="course.courseName"
title="Course Name"></div>
<div my-field
name="credits"
field="course.credits"
title="Credits"></div>
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits.$dirty: {{myForm.credits.$dirty}}</pre>
</form>
</div>
JavaScript:
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: 100,
courseName: 'Programming 201'
};
}])
.directive('myField', ['$compile', '$parse', function($compile, $parse){
// In a real project you'd probably want to use '$templateCache' instead
// of having strings in your code.
var tmpl = $compile('<label>{{title}}</label>');
return {
scope: true,
link: function(scope, element, attr){
scope.title = attr.title;
var newEl = angular.element('<input type="text"/>');
newEl.attr('ng-model', attr.field);
newEl.attr('name', attr.name);
tmpl(scope, function(fieldEl, scope){
$compile(newEl[0].outerHTML)(scope, function(el, scope){
fieldEl.append(el);
element.append(fieldEl);
});
});
}
}
}]);
A note on this example:
This is a very specific situation - generating dynamic form elements - that requires the use of $compile. This is not the "go to" solution when working with Angular inputs and forms - Angular will handle all the common situations with directives, data-binding and everything else the framework provides. Plus, as Marc Kline's comment shows, it looks like at some point Angular will handle dynamic form management itself at some point in the future.
If you were to continue down the path using $compile to generate these form elements then you'd probably want to use the $templateCache to manage your templates so you're not trying to manage template strings inside your directive.
Old question, but in case someone is looking for a way to do what question asked, you can create a directive that will dynamically create the name of the element after $compile'ing it.
An updated version of the answer that #Sly_cardinal posted is here: http://jsfiddle.net/XKYJ3/1/
HTML
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<label for="{{ course.courseName.name }}" ng-bind="course.courseName.title"></label>
<input id="{{ course.courseName.name }}" dynamic-input-name="course.courseName.name" ng-model="course.courseName.value" type="text" required />
<br />
<label for="{{ course.credits.name }}" ng-bind="course.credits.title"></label>
<input id="{{ course.credits.name }}" dynamic-input-name="course.credits.name" ng-model="course.credits.value" type="number" required />
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits_field.$dirty: {{ myForm.credits_field.$dirty }}</pre>
</form>
</div>
Javascript
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: {
title: 'Credits',
value: 100,
name: 'credits_field'
},
courseName: {
title: 'Course name',
value: 'Programming 201',
name: 'course_name_field'
}
};
}])
.directive('dynamicInputName', ['$compile', '$parse', function($compile, $parse){
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-input-name'))(scope);
elem.removeAttr('dynamic-input-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
}]);
I have this form : http://jsfiddle.net/dfJeN/
As you can see the name value for the input is statically set :
name="username"
, the form validation works fine (add something and remove all text from the input, a text must appears).
Then I try to dynamically set the name value : http://jsfiddle.net/jNWB8/
name="{input.name}"
Then I apply this to my validation
login.{{input.name}}.$error.required
(this pattern will be used in an ng-repeat) but my form validation is broken. It is correctly interpreted in my browser (if I inspect the element I saw login.username.$error.required).
Any Idea ?
EDIT: After logging the scope in the console it appears that the
{{input.name}}
expression is not interpolate. My form as an {{input.name}} attribute but no username.
UPDATE: Since 1.3.0-rc.3 name="{{input.name}}" works as expected. Please see #1404
You can't do what you're trying to do that way.
Assuming what you're trying to do is you need to dynamically add elements to a form, with something like an ng-repeat, you need to use nested ng-form to allow validation of those individual items:
<form name="outerForm">
<div ng-repeat="item in items">
<ng-form name="innerForm">
<input type="text" name="foo" ng-model="item.foo" />
<span ng-show="innerForm.foo.$error.required">required</span>
</ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>
Sadly, it's just not a well-documented feature of Angular.
Using nested ngForm allows you to access the specific InputController from within the HTML template. However, if you wish to access it from another controller it does not help.
e.g.
<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';
$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // undefined
console.log($scope.formName.staticName); // InputController
}
}
</script>
<div controller='OuterController'>
<form name='myForm'>
<input name='{{ inputName }}' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>
I use this directive to help solve the problem:
angular.module('test').directive('dynamicName', function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-name'))(scope);
// $interpolate() will support things like 'skill'+skill.id where parse will not
elem.removeAttr('dynamic-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
});
Now you use dynamic names wherever is needed just the 'dynamic-name' attribute instead of the 'name' attribute.
e.g.
<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';
$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // InputController
console.log($scope.formName.staticName); // InputController
}
}
</script>
<div controller='OuterController'>
<form name='myForm'>
<input dynamic-name='inputName' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>
The problem should be fixed in AngularJS 1.3, according to this discussion on Github.
Meanwhile, here's a temporary solution created by #caitp and #Thinkscape:
// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
app.config(['$provide', function($provide) {
$provide.decorator('ngModelDirective', function($delegate) {
var ngModel = $delegate[0], controller = ngModel.controller;
ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
$provide.decorator('formDirective', function($delegate) {
var form = $delegate[0], controller = form.controller;
form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
}]);
Demo on JSFiddle.
Nice one by #EnISeeK.... but i got it to be more elegant and less obtrusive to other directives:
.directive("dynamicName",[function(){
return {
restrict:"A",
require: ['ngModel', '^form'],
link:function(scope,element,attrs,ctrls){
ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
ctrls[1].$addControl(ctrls[0]);
}
};
}])
Just a little improvement over EnlSeek solution
angular.module('test').directive('dynamicName', ["$parse", function($parse) {
return {
restrict: 'A',
priority: 10000,
controller : ["$scope", "$element", "$attrs",
function($scope, $element, $attrs){
var name = $parse($attrs.dynamicName)($scope);
delete($attrs['dynamicName']);
$element.removeAttr('data-dynamic-name');
$element.removeAttr('dynamic-name');
$attrs.$set("name", name);
}]
};
}]);
Here is a plunker trial. Here is detailed explantion
I expand the #caitp and #Thinkscape solution a bit, to allow dynamically created nested ng-forms, like this:
<div ng-controller="ctrl">
<ng-form name="form">
<input type="text" ng-model="static" name="static"/>
<div ng-repeat="df in dynamicForms">
<ng-form name="form{{df.id}}">
<input type="text" ng-model="df.sub" name="sub"/>
<div>Dirty: <span ng-bind="form{{df.id}}.$dirty"></span></div>
</ng-form>
</div>
<div><button ng-click="consoleLog()">Console Log</button></div>
<div>Dirty: <span ng-bind="form.$dirty"></span></div>
</ng-form>
</div>
Here is my demo on JSFiddle.
I used Ben Lesh's solution and it works well for me. But one problem I faced was that when I added an inner form using ng-form, all of the form states e.g. form.$valid, form.$error etc became undefined if I was using the ng-submit directive.
So if I had this for example:
<form novalidate ng-submit="saveRecord()" name="outerForm">
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit">Submit</button>
</form>
And in the my controller:
$scope.saveRecord = function() {
outerForm.$valid // this is undefined
}
So I had to go back to using a regular click event for submitting the form in which case it's necessary to pass the form object:
<form novalidate name="outerForm"> <!--remove the ng-submit directive-->
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit" ng-click="saveRecord(outerForm)">Submit</button>
</form>
And the revised controller method:
$scope.saveRecord = function(outerForm) {
outerForm.$valid // this works
}
I'm not quite sure why this is but hopefully it helps someone.
This issue has been fixed in Angular 1.3+
This is the correct syntax for what you are trying to do:
login[input.name].$invalid
if we set dynamic name for a input like the below
<input name="{{dynamicInputName}}" />
then we have use set validation for dynamic name like the below code.
<div ng-messages="login.dynamicInputName.$error">
<div ng-message="required">
</div>
</div>