I am coding a filter that will format phone numbers in a contact form I've built however for some reason the value in the input is never being updated and I'm not sure what I'm doing wrong.
Here's my HTML:
<div class='form-group'>
<input name='phone' ng-model='home.contact.phone' placeholder='(Area code) Phone' required ng-bind='home.contact.phone | phone' />
</div>
and here's my filter:
(function () {
'use strict'
angular
.module('Allay.phoneFilter', [])
.filter('phone', function () {
return function (phone) {
if(!phone) return '';
var res = phone + '::' // Since this isn't working, I'm doing something super simple, adding a double colon to the end of the phone number.
return res;
}
});
})();
I'm not sure if you need this, but here's the controller:
(function () {
'use strict'
angular
.module('Allay', [
'Allay.phoneFilter'
])
.controller('HomeController', function () {
var home = this;
});
})();
If I add an alert(res) before 'return res' in the filter I see the value I expect '123::', however the value in the input it's self is still just 123.
You need create directive to change your ngModel, like this:
.directive('phoneFormat', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var setvalue = function() {
elem.val(ctrl.$modelValue + "::");
};
ctrl.$parsers.push(function(v) {
return v.replace(/::/, '');
})
ctrl.$render = function() {
setvalue();
}
elem.bind('change', function() {
setvalue();
})
}
};
});
Use in html:
<input name='phone' ng-model='contact.phone' placeholder='(Area code) Phone' required phone-format />
JS Fiddle: http://jsfiddle.net/57czd36L/1/
Your usage of ngBind on the input is not quite correct. From the documentation,
The ngBind attribute tells Angular to replace the text content of the specified HTML element with the value of a given expression, and to update the text content when the value of that expression changes
You do not need to replace the text content of the <input> element, that wouldn't make sense. You can instead extend the formatter pipeline of the NgModelController using a directive like
app.directive('phoneFormat', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$formatters.push(function (value) {
if (value)
return value + '::';
});
}
}
});
Then, in your HTML,
<input ng-model='home.contact.phone' phone-format />
In case you wanted to keep the filter you wrote (for other usages), you can actually re-use it in the directive like
app.directive('phoneFormat', [ '$filter', function ($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$formatters.push($filter('phone'));
}
}
}]);
$filter('phone') simply returns the filter function registered under 'phone'. Here is a Plunker.
Note, this solution will only format data when you change the $modelValue of the NgModelController, for example like
$scope.$apply('home.contact.phone = "123-456-7890"');
If you are looking for something to update/format the value of the input as the user is typing, this is a more complicated task. I recommend using something like angular-ui/ui-mask.
Although a filter module is a good approach, I use an 'A' directive to do the dirty work because changing the element value will affect its ng-model.
However, I would only suggest this kind of solution if your actual data manipulation could sum in 3-4 lines of code; otherwise, a more thorough approach is needed.
This is an example that will delete anything which isn't an integer:
(function () {
'use strict'
angular.module('Allay').directive('phoneValidator', function () {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.element(element).on('keyup', function() {
element.val(element.val().replace(/[^0-9\.]/, ''));
});
}
}
});
})();
And than in your HTML template :
<input name="phone" ng-model="home.contact.phone" placeholder="(Area code) Phone" phoneValidator required/>`
You should remove your "ng-bind" cause you are filtering it and what is presented is what in the ng-model. use value instead.
<input name='phone' ng-model='home.contact.phone | phone' value="{{contact.phone | phone}}" />
see working example: JsFiddle
Related
I am using a directive to build a custom validator and it works fine. But, it was called only once! If my "roleItems" are updated, this directive was not called again! How can it be called every time when "roleItems" are updated?
Here are the markups. And "Not-empty" is my directive.
<form name="projectEditor">
<ul name="roles" ng-model="project.roleItems" not-empty>
<li ng-repeat="role in project.roleItems"><span>{{role.label}}</span> </li>
<span ng-show="projectEditor.roles.$error.notEmpty">At least one role!</span>
</ul>
</form>
This is my directive. It should check if the ng-model "roleItems" are empty.
angular.module("myApp", []).
directive('notEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.notEmpty = function (modelValue, viewValue) {
if(!modelValue.length){
return false;
}
return true;
};
}
};
});
Main purpose of validator is validate ngModel value of user input or model change, so it should be uset to checkbox/textara/input and etc. You cant validate ng-model of everything. Angular is enough intelligent to knows that ng-model makes no sens so he is just ignoring it .
I you wanna change only error message you can check it via .length property. If you wanna make whole form invalid , i suggest you to make custom directive , put it on , and then in validator of this directive check scope.number.length > 0
Basically just adjust your directive code to input element and hide it .... via css or type=hidden, but dont make ngModel="value" its not make sense because ng-model is expecting value which can be binded and overwriteen but project.roleItems is not bindable! so put ng-model="dummyModel" and actual items to another param ...
<input type="hidden" ng-model="dummyIgnoredModel" number="project.roleItems" check-empty>
angular.module("myApp", []).
directive('checkEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.push(function (modelValue, viewValue) {
if(!scope.number.length){
return false;
}
return true;
});
//now we must "touch" ngModel
scope.$watch(function()
{
return scope.number
}, function()
{
ctrl.$setViewValue(scope.number.length);
});
}
};
});
Using ngModel.$formatters and ngModel.$parsers i'm trying to create a directive to change the data-type from model to view and vice versa.
The problem is when i use <input type="number">
In that case toView receives 'undefined' as value, but not string data. ("1234")
If i remove type=number works fine, but the input element is not what i want.
There are any way to get it working?
if not, there are another way to achieve this aim? (the model MUST be string, the input MUST be type="number")
PLUNK
javascript
var app = angular.module('module', []);
app.controller('MainCtrl', function($scope) {
$scope.model = {};
$scope.model.number = "1234";
});
app.directive('numberConverter', function() {
return {
restrict: 'A',
require: 'ngModel',
link: {
pre: function(scope, element, attr, ngModel) {
function toModel(value) {
return "" + value; // convert to string
}
function toView(value) {
return parseInt(value); // convert to number
}
ngModel.$formatters.unshift(toView);
ngModel.$parsers.unshift(toModel);
},
post: function() {}
}
};
});
html
<input type="number" number-converter ng-model="model.number">
Sorry, my original answer wasn't quite right.
Here is an updated directive that will correctly store the value as a string but edit it as a number.
It's important that the priority of the directive be greater than 0 (the default priority) so that it runs after the ngModel directive does. This makes sure that you're adding your formatters and parsers after the defaults (otherwise you're just pushing to an empty list and the default processors will be added after yours anyway).
app.directive('numberConverter', function() {
return {
restrict: 'A',
require: 'ngModel',
priority: 1,
link: function(scope, element, attr, ngModel) {
function toModel(value) {
return "" + value; // convert to string
}
function toView(value) {
return parseInt(value); // convert to number
}
ngModel.$formatters.push(toView);
ngModel.$parsers.push(toModel);
}
};
});
Updated plunkr: http://plnkr.co/edit/l8c1GGBeIX4dawLZJfoC?p=preview
I have created a directive for auto focus on text box
(function () {
'use strict';
angular.module('commonModule').directive('srFocuson',function(){
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs) {
scope.$watch(attrs.focusMe, function (value) {
if (value === true) {
console.log('value=', value);
element[0].focus();
scope[attrs.focusMe] = false;
}
});
}
};
});
})();
And now i want to bind that directive to my text box.I have tried to bind to input field but its not working.
<input placeholder="SR ID, SSN/ITIN, or School ID" sr-focuson="focusMe" type="text"
id="form_ID" name="searchId" autofocus
data-ng-model="vm.searchCriteria.searchId"
maxlength="20" class="form-control">
http://plnkr.co/edit/A39duXhGvCedAaVuB3uQ?p=preview
I made working fiddle with your idea. http://jsfiddle.net/fLaAG/
It's sort of unclear where you would be updating scope.focusMe so I made an explicit button that would set that value to true.
<button type="button" ng-click="Focus()" type="button">Focus</button>
...
$scope.Focus = function() {
$scope.focusMe = true;
};
Also I'm setting up an isolate scope, so I can just watch string I give it.
scope: {
focusMe: '=focusOn'
},
Hope this helps
Here is a method using built-in angular functionality, dug out from the murky depths of the angular docs. Notice how the "link" attribute can be split into "pre" and "post", for pre-link and post-link functions.
Working Example: http://plnkr.co/edit/Fj59GB
// this is the directive you add to any element you want to highlight after creation
Guest.directive('autoFocus', function() {
return {
link: {
pre: function preLink(scope, element, attr) {
console.debug('prelink called');
// this fails since the element hasn't rendered
//element[0].focus();
},
post: function postLink(scope, element, attr) {
console.debug('postlink called');
// this succeeds since the element has been rendered
element[0].focus();
}
}
};
});
<input value="hello" />
<!-- this input automatically gets focus on creation -->
<input value="world" auto-focus />
Full AngularJS Directive Docs: https://docs.angularjs.org/api/ng/service/$compile
I'm trying to use prettyprint plugin for my angularjs app.
But cannot make it works. I create a simple directive and call method prettyPrint(), but the code is not formatted.
FIDDLE: http://jsfiddle.net/Tropicalista/yAv4f/2/
App.directive('test', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).prettyPrint()
}
};
});
I modified your code and i'll update here:
http://jsfiddle.net/yAv4f/6/
html:
<div ng-app="Knob" ng-controller="myCtrl">
<pre class="prettyprint linemus"></pre>
<pre class="prettyprint linemus"><!DOCTYPE html><html lang="en"></html></pre>
</div>
javascript:
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.dom = '<!DOCTYPE html><html lang="en"></html>'
})
App.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(scope.dom));
}
};
});
Basically, you need to use the file prettify.js to control the execution of the prettify() function, with prettyPrintOne() you can execute it in a specific html text.
And to simplify the use of the directive, like prettify stlyle, i'll suggest restric to 'C' a class and change the the directive name to 'prettyprint'
I've expanded on the previous answers and created a jsfiddle with a working directive that responds in realtime to model changes:
http://jsfiddle.net/smithkl42/cwrgLd0L/27/
HTML:
<div ng-app="prettifyTest" ng-controller="myCtrl">
<div>
<input type="text" ng-model="organization.message" />
</div>
<prettify target="organization"><pre><code class="prettyprint">console.log('{{target.message}}');
</code>
</pre>
</prettify>
</div>
JS:
var App = angular.module('prettifyTest', []);
App.controller('myCtrl', function ($scope) {
$scope.organization = {
message: 'Hello, world!'
};
});
App.directive('prettify', ['$compile', '$timeout', function ($compile, $timeout) {
return {
restrict: 'E',
scope: {
target: '='
},
link: function (scope, element, attrs) {
var template = element.html();
var templateFn = $compile(template);
var update = function(){
$timeout(function () {
var compiled = templateFn(scope).html();
var prettified = prettyPrintOne(compiled);
element.html(prettified);
}, 0);
}
scope.$watch('target', function () {
update();
}, true);
update();
}
};
}]);
h/t to #DanielSchaffer (see Template always compiles with old scope value in directive).
Angular already has this filter built-in for JSON:
<pre>
{{data | json}}
</pre>
If you want to make your own directive, you can use the JSON object directly:
app.filter('prettyJSON', function () {
function syntaxHighlight(json) {
return JSON ? JSON.stringify(json, null, ' ') : 'your browser doesnt support JSON so cant pretty print';
}
return syntaxHighlight;
});
With markup
<pre>
{{data | prettyJSON}}
</pre>
I would like to make a small addition to the directive by #carlosmantilla
You can achieve the same thing without creating the scope variable. I have added this correction on github
This should work properly I assume.
http://jsfiddle.net/yAv4f/143/
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.text = "function f1(){int a;}";
})
function replaceText(str)
{
var str1 = String(str);
return str1.replace(/\n/g,"<br/>");
}
app.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(replaceText(element.html()),'',true));
}
};
});
I struggled with this issue for quite a while and wanted to chime in here, albeit much later than everyone else (for real though, who's still using AngularJS in late 2017? This guy.) My specific use-case was where I have code (xml) being dynamically loaded on the page which needed to be pretty printed over and over again.
This directive will take in your code as an attribute, remove the prettyprinted class that's added to the element right after you run prettyPrint(). It will watch for changes on the inputted code from the parent's scope and run the code again when changes occur.
Only dependency is that you have Google's code-prettify. I had it self-hosted, hence the PR.prettyPrint() (as instructed in the docs as of sept 2017).
The directive fully encapsulates the needed Google code-prettify functionality for dynamic content.
angular.module('acrSelect.portal.directives')
.directive('prettyPrint', ['$timeout', function($timeout) {
return {
restrict: 'E',
scope: {
'code': '=',
},
template: '<pre ng-class="{prettyprint: code}">{{ code }}</pre>',
link: function (scope, element, attr) {
scope.$watch('code',function(){
$timeout(function() {
//DOM has finished rendering
PR.prettyPrint();
element.find(".prettyprint").removeClass("prettyprinted");
});
});
}
}
}
]);
The html in the parent template might look
<pretty-print code="selectedCode" ng-show="codeIsSelected"></pretty-print>
Hope this helps another poor soul!
I'm building an application using AngularJS and UniformJS. I'd like to have a reset button on the view that would reset my select's to their default value. If I use uniform.js, it isn't working.
You can examine it here:
http://plnkr.co/edit/QYZRzlRf1qqAYgi8VbO6?p=preview
If you click the reset button continuously, nothing happens.
If you remove the attribute, therefore no longer using uniform.js, everything behaves correctly.
Thanks
UPDATE:
Required the use of timeout.
app.controller('MainCtrl', function($scope, $timeout) {
$scope.reset = function() {
$scope.test = "";
$timeout(jQuery.uniform.update, 0);
};
});
Found it. For the sake of completeness, I'm copying my comment here:
It looks like Uniform is really hacky. It covers up the actual select element, and displays span instead. Angular is working. The actual select element's value is changing, but the span that Uniform displays is not changing.
So you need to tell Uniform that your values have changed with jQuery.uniform.update. Uniform reads the value from the actual element to place in the span, and angular doesn't update the actual element until after the digest loop, so you need to wait a little bit before calling update:
app.controller('MainCtrl', function($scope, $timeout) {
$scope.reset = function() {
$scope.test = "";
$timeout(jQuery.uniform.update, 0);
};
});
Alternatively, you can put this in your directive:
app.directive('applyUniform',function($timeout){
return {
restrict:'A',
require: 'ngModel',
link: function(scope, element, attr, ngModel) {
element.uniform({useID: false});
scope.$watch(function() {return ngModel.$modelValue}, function() {
$timeout(jQuery.uniform.update, 0);
} );
}
};
});
Just a slightly different take on #john-tseng's answer. I didn't want to apply a new attribute to all my check-boxes as we had quite a few in the application already. This also gives you the option to opt out of applying uniform to certain check-boxes by applying the no-uniform attribute.
/*
* Used to make sure that uniform.js works with angular by calling it's update method when the angular model value updates.
*/
app.directive('input', function ($timeout) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attr, ngModel) {
if (attr.type === 'checkbox' && attr.ngModel && attr.noUniform === undefined) {
element.uniform({ useID: false });
scope.$watch(function () { return ngModel.$modelValue }, function () {
$timeout(jQuery.uniform.update, 0);
});
}
}
};
});
Please try blow code.
app.directive('applyUniform', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
if (!element.parents(".checker").length) {
element.show().uniform();
// update selected item check mark
setTimeout(function () { $.uniform.update(); }, 300);
}
}
};
});
<input apply-uniform type="checkbox" ng-checked="vm.Message.Followers.indexOf(item.usrID) > -1" ng-click="vm.toggleSelection(item.usrID)" />