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.
Related
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.
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
I have a form label containing an input:
<label data-live-email-check="http://www.example-service-uri.com/">
<span class="embedded-label">Email</span>
<input ng-model="formData.email"
type="email"
name="email"
placeholder="Email"
required/>
<span class="message" ng-show="emailValidationMessage">{{emailValidationMessage}}</span>
</label>
I want to create a directive that takes the URL provided by the data-live-email-check attribute and sends the email to that URL, validating whether or not it already exists.
angular.module("App").directive("liveEmailCheck", [function () {
return {
restrict: "A",
scope: {
ngModel: "="
},
link: function (scope, element, attrs) {
scope.$watch(
function(){
return scope.ngModel
},
function(newVal){
console.log(newVal);
}
);
}
}
}]);
I just want to watch the model on the input, and fire a request when it updates.
Since the directive is defined on the label element ngModel is not properly bound. What am I doing wrong? My watch expression is not logging anything because it never fires.
I know I could manually grab the input, but that just seems like I'd be breaking the "angular pattern" that I feel so "bound" by. The frustrating thing is that there seems to be so many ways to do everything in Angular that I can never tell if I'm approaching a problem correctly or not.
--Edit--
To provide the solution that I personally would take (out of ignorance of a "better" way), would be the following:
angular.module("App").directive("liveEmailCheck", [function () {
return {
restrict: "A",
require: ["^form"],
link: function (scope, element, attrs, ctrl) {
var formCtrl = ctrl[0];
scope.formEl = formCtrl[element.find("input").attr("name")];
scope.$watch(function(){return scope.formEl.$valid},
function(newVal){
console.log(newVal);
});
}
}
}]);
This WORKS, but I feel like it "breaks the angular rules".
A custom validation is written like this:
'use strict';
angular.module('App')
.directive('liveEmailCheck', function (){
return {
require: 'ngModel',
link: function (scope, elem, attr, ngModel){
ngModel.$validators.liveEmailCheck= function (value){
//Your logic here or in a factory
};
}
};
});
and then on your HTML it goes like
<input type="email" live-email-check="liveEmailCheck>
Basically you add your own validation to the set of build-in validations of angular.
What you need here is an ng-model asyncValidator. Here is a simple implementation of such a directive.
angular.module("App").directive('asyncEmailValidator', function ($http) {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var emailValidationUrl = attrs.asyncEmailValidator;
ngModel.$asyncValidators.emailValidator = function (modelValue, viewValue) {
var value = modelValue || viewValue;
// NOTE: don't forget to correctly add the value to the url
return $http(emailValidationUrl + value).then(function (validationResponse) {
// NOTE: return rejected promise if validation failed else true
});
};
}
};
});
How you can use it in your case:
<label>
<span class="embedded-label">Email</span>
<input ng-model="formData.email"
async-email-validator="http://www.example-service-uri.com/"
type="email"
name="email"
placeholder="Email"
required/>
<span class="message" ng-show="<FormNameHere>.email.$error.emailValidator">
{{emailValidationMessage}}
</span>
</label>
This will be the right solution because it is implemented with angular ng-model validation which considers the validity of the model too.
Here's a jsfiddle example of what I'm trying to accomplish.
I'm trying to build a US phone number input where the view displays as (333) 555-1212, but the model binds to the numeric integer 3335551212.
My intention is to add custom validators to NgModelController which is why I have require: ng-model; there are simpler solutions without the isolate scope and NgModelController, but I need both.
You'll see an immediate error in the console: Error: Multiple directives [ngModel, ngModel] asking for 'ngModel' controller on: <input ng-model="user.mobile numeric" name="telephone" type="tel"> -- thought I was using an isolate scope here...
Thank you for looking #mimir137 but I appear to have solved it:
http://jsfiddle.net/hr121r18/8/
The directive was using replace: true, which ends up with this structure:
<form ng-controller="FooCtrl" class="ng-scope">
<p>Enter US phone number</p>
<input ng-model="user.mobile numeric" name="telephone" type="tel">
</form>
Both the template and the markup called for ng-model which led to the symptomatic error in the problem description. Once I removed that, it leads to this markup (note the wrapper element phone-number):
<form ng-controller="FooCtrl" class="ng-valid ng-scope ng-dirty ng-valid-parse" abineguid="BC0D9644F7434BBF80094FF6ABDF4418">
<p>Enter US phone number</p>
<phone-number ng-model="user.mobile" class="ng-untouched ng-valid ng-isolate-scope ng-dirty ng-valid-parse">
<input ng-model="numeric" name="telephone" type="tel" class="ng-valid ng-dirty ng-touched">
</phone-number>
</form>
But removing this required changes to $render; the elem passed into the link function is now phone-number and so you need to dig to grab the input inside it and set the value on that:
ngModel.$render = function () {
elem.find('input').val($filter('phonenumber')(ngModel.$viewValue));
};
There were a few other issues. $render() also needed to be called from the watcher.
Final:
var app = angular.module('myApp', []);
// i want to bind user.mobile to the numeric version of the number, e.g. 3335551212, but
// display it in a formatted version of a us phone number (333) 555-1212
// i am trying to make the directive's scope.numeric to have two-way binding with the controller's
// $scope.user.mobile (using isolate scope, etc.).
app.controller('FooCtrl', function ($scope) {
$scope.user = {
mobile: 3335551212
};
});
app.directive('phoneNumber', ['$filter', function ($filter) {
return {
restrict: 'E',
template: '<input ng-model="numeric" name="telephone" type="tel">',
require: 'ngModel',
scope: {
numeric: '=ngModel'
},
link: function (scope, elem, attrs, ngModel) {
// update $viewValue on model change
scope.$watch('numeric', function () {
ngModel.$setViewValue(scope.numeric);
ngModel.$render();
});
// $modelValue convert to $viewValue as (999) 999-9999
ngModel.$formatters.push(function (modelValue) {
return $filter('phonenumber')(String(modelValue).replace(/[^0-9]+/, ''));
});
// $viewValue back to model
ngModel.$parsers.push(function (viewValue) {
var n = viewValue;
if (angular.isString(n)) {
n = parseInt(n.replace(/[^0-9]+/g, ''));
}
return n;
});
// render $viewValue through filter
ngModel.$render = function () {
elem.find('input').val($filter('phonenumber')(ngModel.$viewValue));
};
}
};
}]);
app.filter('phonenumber', function () {
return function (number) {
if (!number) {
return '';
}
number = String(number);
var formattedNumber = number;
var c = (number[0] === '1') ? '1 ' : '';
number = number[0] === '1' ? number.slice(1) : number;
var area = number.substring(0, 3),
exchange = number.substring(3, 6),
subscriber = number.substring(6, 10);
if (exchange) {
formattedNumber = (c + '(' + area + ') ' + exchange);
}
if (subscriber) {
formattedNumber += ('-' + subscriber);
}
return formattedNumber;
}
});
HTML
<form ng-controller="FooCtrl">
<p>Enter US phone number</p>
<phone-number ng-model='user.mobile'></phone-number>
</form>
I created this fiddle that gets rid of most of your errors coming up in the console. Hopefully this will at least be able to put you on the right track.
I changed the template so that you can see that the filter is actually working.
It now has the typical {{ngModel | FilterName}} in plain text underneath the textbox.
The only real issue is displaying it in the textbox. I'm sure you will have no problem with that. I will check in the morning just in case you still have questions regarding this.
Edit: Alright it appears you have solved it already. Great job!
I Created js fiddle: Fiddle
I create a form with some ng-options in it, and it have strange behavior when you use the button instead of mouse (just click on the textbox and press "tab" and you can select it using arrow key).
<form ng-controller="MyApp" id="Apps" name="Apps" ng-submit="SendApp()" role="form" novalidate>
<input type="text" name="title" ng-model="Info.Title" />
<select id="Formula" ng-model ="Info.Genre" ng-change= "ChangeGenre()"
ng-options="id as name for (id, name) in Genre" blank></select>
<select class="form-control" ng-model ="Info.Program"
ng-options="Program as Name for (Program, Name) in Program" ng-change="ChangeProgram()" blank></select>
<h3>{{Info.Genre}}</h3>
<h3>{{Info.Program}}</h3>
<button type=submit>Submit this </button>
</form>
Javascript:
var theApp = angular.module("TheApp", []);
theApp.controller("MyApp", ['$scope', function($scope){
$scope.Program = {"1":"Music","2":"Theater","3":"Comedy"};
$scope.Genre = {"1":"Mystery", "2":"Thriller", "3":"Romance"};
$scope.ChangeProgram = function(){
alert($scope.Info.Program + " " + $scope.Info.Genre);
}
$scope.ChangeGenre = function (){
console.log($scope.Info.Genre);
}
$scope.SendApp = function(){
alert($scope.Info.Program + " " + $scope.Info.Genre);
}
}]);
The ng-model are not updated when you select the first options on First try.
What's Wrong, and How To Fix this?
Update:
As Mentioned on comment below, To reproduce, enter mouse into textfield, tab to combobox and try to select the second option (Thriller) using keyboard. This will fail on the first attempt, once the third or first option is selected, the second option is also recognized.
Using the the directive proposed here, this works for me:
theApp.directive("select", function() {
return {
restrict: "E",
require: "?ngModel",
scope: false,
link: function (scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
element.bind("keyup", function() {
element.triggerHandler("change");
})
}
}
})
I forked the fiddle.