Two way binding Directive + Controller - javascript

I am dynamically adding a text field in DOM (from directive link function) & want to grab entered value and push it to controller scope object, but its always giving me undefined, below is my code:
<div class="input-group">
<span class="fieldIcon input-group-addon"><i class="fa fa-sticky-note" aria-hidden="true"></i></span>
<select name="addlist[]" multiple="multiple">
<option ng-repeat="options in optionList">{{options.label}}</option>
</select>
</div>
<script type="text/javascript">
angular.module('myapp')
.controller('AddContactController',[ '$scope', function ($scope) {
$scope.optionList = [{label: 'NewList'}];
$scope.addOption = function(optionList) {
console.log('List:', optionList); // its giving undefined
scope.optionList.push(optionList);
}
}])
.directive('optionList', ['$compile', function ($compile) {
return {
restrict: 'E',
templateUrl: '/templates/int_optionList.html',
controller: 'AddContactController',
link: function(scope, element, attrs) {
// Adding input field and on click of a button controllers addOption function should be called with the text field value
var addListField = '<input class="form-control" type="text" ng-modal="addList" name="addList" placeholder="Add new list...">'+
+'<button class="btn btn-default" type="button" ng-click="addOption()">';
addListField = $compile(addListField)(scope);
$(element).find('.multiselect-container').prepend(addListField);
}
}
}]);
</script>
Now here in addOption function I am getting optionList value as undefined.

Error is human :).
Replace ng-modal by ng-model in your directive

AngularJS expects models to be in object form, setting ng-modal="addList" to ng-modal="data.addList" might help.

Related

How can I access the responding element where ng-disabled is attached to?

Is there a clean way to access the element where the ng-disabled property is attached to inside the function that is given to it?
Html element:
<input class="needsDisabling" ng-disabled="isFieldDisabled()" type="text">
isFieldDisabled function in controller:
How can I access the element where the ng-disabled prop is attached to in this function ?
$scope.isFieldDisabled = function () {
// How can I access the element where the ng-disabled prop is attached to in this function ?
// if (element has class "needsDisabling")
// return true;
}
I am looking for another way then passing the element as a parameter
in the function.
JSFiddle
ngDisabled was designed to work with your model (#2) and does not allow to manipulate your DOM elements manually in your controllers (which is considered a bad practice in angularjs). But anyway you can still create your custom directive and manipulate disabled property manually (#1):
angular.module('myApp',[])
.directive('myDisabled', [function () {
var myDisabled = {
restrict: 'A',
scope: false,
link : function(scope, element, attrs, ngModelCtrl){
scope.$watch(function() {
return scope.$eval(attrs.myDisabled).apply(scope, element);
}, function(val) {
element.prop('disabled', val);
});
}
}
return myDisabled;
}])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
$scope.isFieldDisabled = function (element) {
return angular.element(element).hasClass('disabled');
}
$scope.inputs = [{}, { disabled: true }, {}];
$scope.isInputDisabled = function (input){
return !!input.disabled;
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.angularjs.org/1.6.4/angular.js" ></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<h2>#1</h2>
<input class="needsDisabling disabled" my-disabled="isFieldDisabled" type="text"/>
<input class="needsDisabling" my-disabled="isFieldDisabled" type="text"/>
<input class="needsDisabling disabled" my-disabled="isFieldDisabled" type="text"/>
<h2>#2</h2>
<div>
<input type="text" ng-repeat="input in inputs" ng-disabled="isInputDisabled(input)" ng-model="input.value"/>
</div>
</div>
</div>
You cannot pass the element to the controller's function in angularjs, but why not take advantage of the ng-class directive as your class and disabled values depend on each other?
ng-class="{needsDisabling: isDisabled}" ng-disabled="isDisabled"
I cannot think of a reason why you should not do it like this because;
If the class is not dynamic then there is no need for ng-disabled.
You could just use disabled="disabled" but I guess that is not the reason.
If the value of class IS dynamically switched outside of angular's
knowledge, then that part of the solution is problematic and should
be changed.
Another reason to deal with this issue in angularjs itself is to create your own directive as someone here pointed out.

Finding all ng-model elements of a particular child elements

I have written on directive I just want all the ng-model elements inside the element where the directive is placed
This is my html element with loggerhelp directive
<input type="text" loggerhelp />
This is my directive
angular
.module('app').directive('loggerhelp', loggerhelp);
loggerhelp($mdDialog) {
var Popupdirective = {
restrict: 'A',
scope: false,
link: popupController
}
return Popupdirective
function popupController(scope, element, attr) {
// Here i want all the child ng-model elements.
console.log(element)
alert();
}
}
Have you tried searching by attribute like element.find("[ng-model]")? element is the jqLite-wrapped element that this directive matches so find and the selectors should work for you.
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
}])
.directive('loggerhelp', function loggerhelp() {
var loggerhelpDirective = {
restrict: 'A',
scope: false,
link: loggerhelpController
}
return loggerhelpDirective
function loggerhelpController(scope, element, attr) {
var innerElementsWithNgModel = element.find("[ng-model]");
console.log(innerElementsWithNgModel.length);
console.log(angular.element(innerElementsWithNgModel[2]).attr('ng-model'));
//so now you have an array with all the elements that have ng-model attr
//you can attach event handler function for blur
innerElementsWithNgModel.on('blur', function(){
var ngModelAttr = angular.element(this).attr('ng-model');
//we can evaluate this now using $eval
if (ngModelAttr){
console.log(scope.$eval(ngModelAttr));
}
});
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//code.angularjs.org/1.6.2/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl as $ctrl">
<div loggerhelp>
<input type="text" ng-model="$ctrl.model1" />
<input type="text" ng-model="$ctrl.model2" />
<input type="text" ng-model="$ctrl.model3" />
<input type="text" ng-model="$ctrl.model4" />
</div>
</div>
</div>
UPDATE 1: Working snippet to illustrate how it works.
UPDATE 2: Now we can attach event handler function for blur event using .on() and evaluate the attribute using $eval().
You can achieve this by fourth parameter of directive , i.e NgModelController, Read doc.
Code should be like:
link: function(scope, element, attr, ngModel) {
$timeout(function() {
console.log(element);
console.log(ngModel.$viewValue);
}, 0)
}
See this example fiddle

Directive to format text to be displayed

I am playing with AngularJS directive. I want to format the data which is going to be displayed. This is what I am doing:
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<input ng-model="data" type="text" test />
<input type="button" ng-click="change()" value="Change"> {{data}}
<br>Hello <span ng-bind="data" test></span>
</div>
JS:
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.data = 'yellow';
$scope.change = function() {
$scope.data = 'black';
};
})
.directive('test', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$formatters.push(function(value) {
//formats the value for display when ng-model is changed
return 'brown';
});
ngModel.$parsers.push(function(value) {
//formats the value for ng-model when input value is changed
return 'green';
});
}
};
});
I am able to format the data for the model and display it for input text. But I am not able to format the data for the span which is bound to a model. span is showing the model as it is. I don't know why the span is showing the value of the model. I want to show the formatted value to the span as well. Here is the jsFiddle.
You can use ng-model for only input. If you want to change a text in a div,span,label or any other element without input you can use seperators . {}
When you create a variable in $scope you can show it in your html.
<div ng-app="myApp" ng-controller="myCtrl">
<input ng-model="data" type="text" test />
<input type="button" ng-click="change()" value="Change"> {{data}}
<br>Hello <span test>{data}</span>
</div>
Fiddle
I think it is working perfectly fine only thing is you are not taking the right example here.
First of all please read below:
Formatters change how model values will appear in the view.
Parsers change how view values will be saved in the model.
and now replace the code below
ngModel.$formatters.push(function(value) {
//formats the value for display when ng-model is changed
return 'brown';
});
ngModel.$parsers.push(function(value) {
//formats the value for ng-model when input value is changed
return 'green';
});
with
ngModel.$formatters.push(function(value) {
//formats the value for display when ng-model is changed
return value.toLowerCase();
});
ngModel.$parsers.push(function(value) {
//formats the value for ng-model when input value is changed
return value.toUpperCase();
});
I hope this help!!

Calling angularjs function on text input based on length

I have a text box. I would like to call a method inside controller only when user has filled in 'n' or more number of characters in the textbox.
Can someone please give me pointers on how to approach this?
Thanks
Id recommend just using ngChange and binding to an evaluation function. Below is a sample
angular.module('inputChange', [])
.controller('TextInputController', ['$scope', function ($scope) {
var inputMin = 3;
$scope.someVal = '';
$scope.result = '';
$scope.textChanged = function() {
if ($scope.someVal.length >= inputMin) executeSomething()
else $scope.result = '';
};
function executeSomething() {
$scope.result = $scope.someVal;
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="inputChange" ng-controller="TextInputController">
<input type="text" ng-model="someVal" ng-change="textChanged()" ng-Trim="false" />
<br />
someVal: <span ng-bind="someVal"></span>
<br />
Result: <span ng-bind="result"></span>
<br />
someVal Length: <span ng-bind="someVal.length"></span>
<br />
Result Length: <span ng-bind="result.length"></span>
</div>
You could simply achieve this by using ng-keyup directive
ng-keyup="(1myNgModel.length >= n) && myFunction()"
Desired function will only gets called only if length of model is greater than equal to n length
Working Plunkr
Though the better version would be having ng-model-options with debounce time, so that it will reduce number of value change. After that we can easily use ng-change directive to fire function.
<input type="text" ng-model="myNgModel"
ng-change="(myNgModel.length >= 3) && myFunction()"
ng-model-options="{ debounce: 200 }" />
Updated Demo
You can add a directive to your element and $watch for model changes. Then you can fire any logic you wish when your model has changed and has a value. In this case, lets call our model expression. Here is an example for a <textarea> element. This approach can just as well be used for an <input /> element as well.
<textarea watcher ng-model="expression"></textarea>
app.directive('watcher', [function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
scope.$watch(attrs.ngModel, function (v) {
if(v) {
// you have a value
} else {
// no value
}
});
}
}
}]);
JSFiddle Example
A good way to do this is to use a directive. Here's how it might be done:
view:
<div ng-app="foo" ng-controller="fooController">
<textarea text-length-handler="doThing()" text-length="6" ng-model="text">
</textarea>
</div>
js:
angular.module('foo', [])
.directive('textLength', function(){
return {
restrict: 'A',
require: 'ngModel',
scope: {
textLengthHandler: '&'
},
link: function ($scope, $element, $attrs, ctrl) {
var limit = parseInt($attrs.textLength);
var handler = function(){
if (ctrl.$modelValue.length >= limit) {
$scope.textLengthHandler()
}
};
$element.on('keypress', handler);
// remove the handler when the directive disappears
$scope.$on('destroy', function(){
$element.off('keypress', handler)
});
}
}
})
Fiddle here:
http://jsfiddle.net/dtq0mz8m/
If you tie the input field to a variable using ngModel, you can watch it from the controller (is not very elegant, though) using $watch or $observe whenever it changes, and check the length.

Validating the date field's using angularJS

I am using the angularJS for validation purpose in my project and i know to validate the normal fields. Now, i have the date field by the parameter passing in the name attribute. In some cases user can click the 'Add Date' button for adding more dates. So, in this case i have written the code by passing the parameter with the name attribute. Because of that i couldn't get the name field to validate.
Even if user add more date fields to enter i want to validate the all date fields.
Here is my code i am using,
<div class="col-md-12 col-sm-12 col-xs-12 table-responsive">
<table class="table table-striped table-hover">
<tr ng-repeat="dates in hall.hallDates">
<td>
<div class="col-md-6 col-sm-6 p-0 p-t-10">
Select Date<span style="color: red">*</span>
</div>
<div class="col-md-6 col-sm-6 p-0 p-t-10">
<input type="text" ng-model="dates.hallDate"
name="hallDate{{ $index }}" class="form-control"
placeholder="DD-MM-YYYY" mydatepicker readonly="true"
ng-class="{ validateFields: submitted && createHallForm.date.$invalid }"
required>
<div ng-show="submitted && createHallForm.date.$invalid">
<span class="validateFields" ng-show="createHallForm.date.$error.required" >Please enter the date</span>
</div>
</div>
How to get the name field with parameter to apply validation for n number of fields?
Can anyone help me to know about this?
It's likely that you're running Angular 1.2 or below. In those versions you cannot dynamically set the name attribute of an element. You can confirm this by looking in the rendered DOM and still seeing
name="hallDate{{ $index }}" after render.
See the related GitHub issue here: https://github.com/angular/angular.js/issues/1404
Solution 1
Upgrade to 1.3.x
Solution 2
Use the following workaround as posted in the GitHub issue referenced above.
// Your app module
angular.module('app', [])
// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
.config(['$provide', function($provide) {
$provide.decorator('ngModelDirective', ['$delegate', 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', ['$delegate', 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;
}]);
}]);

Categories