AngularJs call method in template directive - angularjs

I have following directive that renders a number value like 3 as 3.xxx
depending on argument decimals
app.directive("myValue", () => {
return {
template: '{{value.toFixed(decimals)}}',
scope: {
'value': '=myValue',
'decimals': '=myValueDecimals'
},
link: function () {
}
}
});
Now I want to include handling of special values like undefined,NAN etc.
I already have a function that should replace value.toFixed(decimals)
but I cannot figure out how to call a function directly in the directive.
Update: the view value is being changed dynamically so it should be refreshed whenever the value changes

You need to create viewValue in the directive's scope and assign handled value to it. For typescript you might want to do something like this:
interface IValueScope extends IScope {
viewValue:number;
}
app.directive("myValue", () => {
return {
template: '{{viewValue}}',
scope: {
'value': '=myValue',
'decimals': '=myValueDecimals',
},
link: function ($scope:IValueScope) {
$scope.viewValue = $scope.value == null || Number.isNaN($scope.value)
? 0
: $scope.value.toFixed($scope.decimals);
}
}
});
const app = angular.module('app', []);
app.component('home', {
template: '<span><button ng-click="$ctrl.inc()">Inc</button> <data-val data-my-value="$ctrl.v" data-my-value-decimals="2" ></my-value> </span>',
controller: function() {
this.v = 1;
this.inc = () => this.v += 1;
}
});
app.directive("val", () => {
return {
template: '{{viewValue}}',
scope: {
'value': '<myValue',
'decimals': '<myValueDecimals',
},
link: function($scope) {
const newValue = (v) => v == null || Number.isNaN(v) ?
0 :
v.toFixed($scope.decimals)
$scope.viewValue = newValue($scope.value);
const cleanup = $scope.$watch('value', (value) => {
$scope.viewValue = newValue(value)
});
$scope.$on('$destroy', () => cleanup());
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="app">
<home></home>
</div>

Related

Angular JS watch factory variable

I have a factory variable which is shared by two directives. One directives changes it and I need it to update the value in other directive as well.
This is my factory code
commonApp.factory('setSmsMessage', [function () {
var factoryObj = {
data: {
isSms: false
},
setSms: function () {
factoryObj.data.isSms = true;
},
reset: function () {
factoryObj.data.isSms = false;
}
};
return factoryObj;
}]);
This is my one directive
commonApp.directive('osSendMessage', ['setSmsMessage',
function (setSmsMessage) {
return {
restrict: 'A',
scope: {
sent: '='
},
link: function (scope, element, attrs) {
},
controller: function ($scope) {
$scope.setSmsMessage = setSmsMessage.data;
$scope.$watch('setSmsMessage.isSms', function (newValue, oldValue, scope) {
console.log(setSmsMessage.data.isSms);
}, true);
},
templateUrl: "/Static/js/AngularApps/MessageCenter/sendmessage/Templates/sendmessages.html"
};
}]);
This is my other directive which sets the variable
commonApp.directive('osMessageCenterMenu', ['setSmsMessage', function (setSmsMessage) {
return {
restrict: 'A',
scope: {
messages: "="
},
controller: function ($scope) {
},
link: function (scope) {
// opening sms
$("#toggleSendSmsMessage").click(function () {
scope.openSendSms();
});
scope.openSendSms = function () {
console.log("from menu: " + setSmsMessage.data.isSms);
setSmsMessage.setSms();
console.log("from menu: " + setSmsMessage.data.isSms);
$("#SendMessageForm").toggle();
}
// end here
,
templateUrl: "/Static/js/AngularApps/MessageCenter/Templates/messageCenterMenu.html"
};
}]);
But the value is not updated in the other controller which is listening to it
A better way would be to store the isSms property in a global object like so:
commonApp.factory('setSmsMessage', [function ()
{
var factoryObj = {
data: {
isSms: false
},
setSms: function ()
{
factoryObj.data.isSms = true;
},
reset: function ()
{
factoryObj.data.isSms = false;
}
};
return factoryObj;
}]);
Now in your controller link the $scope to the factories global object which holds the isSms property.
// Link setSmsMessage to the factories data object
$scope.setSmsMessage = setSmsMessage.data;
// Now call the setSms() function. The $scope.setSmsMessage will be updated after the call.
setSmsMessage.setSms();
// You can now watch for changes in the $scope like so
$scope.$watch('setSmsMessage.isSms', function (newValue, oldValue, scope) {
console.log(setSmsMessage.data.isSms);
}, true);
Working demo
(function() {
var commonApp = angular.module("app", []);
commonApp.factory('setSmsMessage', [function ()
{
var factoryObj = {
data: {
isSms: false
},
setSms: function ()
{
factoryObj.data.isSms = true;
},
reset: function ()
{
factoryObj.data.isSms = false;
}
};
return factoryObj;
}]);
commonApp.controller("smsController", function($scope, setSmsMessage) {
// Link setSmsMessage to the factories data object
$scope.setSmsMessage = setSmsMessage.data;
// Now call the setSms() function. The $scope.setSmsMessage will be updated after the call.
setSmsMessage.setSms();
$scope.reset = function()
{
setSmsMessage.reset();
}
$scope.$watch('setSmsMessage.isSms', function (newValue, oldValue, scope) {
console.log(setSmsMessage.data.isSms);
}, true);
});
commonApp.directive('osMessageCenterMenu', ['setSmsMessage', function (setSmsMessage)
{
return {
restrict: 'A',
scope: {
messages: "="
},
controller: function ($scope)
{
},
link: function (scope)
{
console.log(scope);
// opening sms
$("#toggleSendSmsMessage").click(function ()
{
scope.openSendSms();
});
scope.openSendSms = function ()
{
console.log("from menu: " + setSmsMessage.data.isSms);
setSmsMessage.setSms();
console.log("from menu: " + setSmsMessage.data.isSms);
$("#SendMessageForm").toggle();
}
}
}
}]);
})()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="smsController">
<div os-message-center-menu>
<button id="toggleSendSmsMessage">reset message</button>
</div>
</div>
</div>
you can also work with $rootScope in your factory and emit an event with custom data and then subscribe to the event in all your controllers
Also, try naming your factory based on functionality, for obvious
reasons.
Following is what am trying to say :-
(function() {
angular
.module("myApp", [])
.factory('smsMessage', ['$rootScope', function($rootScope) {
var factoryObj = {
data: {
isSms: false
},
setSms: function() {
factoryObj.data.isSms = true;
$rootScope.$emit('SET', {isSms : true});
},
reSet: function() {
factoryObj.data.isSms = false;
$rootScope.$emit('RE-SET', {isSms : false});
}
};
return factoryObj;
}])
.controller("controllerOne", function($scope, $rootScope, smsMessage) {
$scope.setSmsMessage = smsMessage.data;
$scope.set = function(){
smsMessage.setSms();
}
$scope.reset = function(){
smsMessage.reSet();
}
$rootScope.$on('SET', function(event, data) {
alert('SET in controllerOne');
$scope.setSmsMessage = data.isSms;
});
$rootScope.$on('RE-SET', function(event, data) {
alert('RE-SET in controllerOne');
$scope.setSmsMessage = data.isSms;
});
})
.controller("controllerTwo", function($scope, $rootScope, smsMessage) {
$scope.setSmsMessage = smsMessage.data;
$scope.set = function(){
smsMessage.setSms();
}
$scope.reset = function(){
smsMessage.reSet();
}
$rootScope.$on('SET', function(event, data) {
alert('SET in controllerTwo');
$scope.setSmsMessage = data.isSms;
});
$rootScope.$on('RE-SET', function(event, data) {
alert('RE-SET in controllerTwo');
$scope.setSmsMessage = data.isSms;
});
});
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="controllerOne">
<label>Controller One</label>
<button ng-click="set()">SET</button>
<button ng-click="reset()">RE-SET</button>
</div>
<div ng-controller="controllerTwo">
<label>Controller Two</label>
<button ng-click="set()">SET</button>
<button ng-click="reset()">RE-SET</button>
</div>
</div>

How to redisplay directive after Javascript function executes

I have an AngularJS Directive defined in a Javascript file that looks like this:
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
var directive = {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope: true
};
return directive;
function link(scope, element, attrs) {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
}
}
})();
and in the template URL of the directive there is a button that calls a Javascript function which looks like this:
function upload(files) {
var formData = new FormData();
angular.forEach(files, function (file) {
formData.append(file.name, file);
});
return fileManagerClient.save(formData)
.$promise
.then(function (result) {
if (result && result.files) {
result.files.forEach(function (file) {
if (!fileExists(file.name)) {
service.files.push(file);
}
});
}
appInfo.setInfo({ message: "files uploaded successfully" });
return result.$promise;
},
function (result) {
appInfo.setInfo({ message: "something went wrong: " +
result.data.message });
return $q.reject(result);
})
['finally'](
function () {
appInfo.setInfo({ busy: false });
service.status.uploading = false;
});
}
Once I select files for upload and click the upload button I need to reload the directive or somehow get it back to it's initial state so I can upload additional files. I'm relatively new to AngularJS and I'm not quite sure how to do this. Any help is much appreciated.
Thanks,
Pete
You just need to create a reset method. Also, you may want to call the parent controller function.
Using answer from this
ngFileSelect.directive.js
...
.directive("onFileChange",function(){
return {
restrict: 'A',
link: function($scope,el){
var onChangeHandler = scope.$eval(attrs.onFileChange);
el.bind('change', onChangeHandler);
}
}
...
fileUploader.directive.js
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
return {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope:{
onSubmitCallback: '&',
onFileChange: '&'
}
};
function link(scope, element, attrs) {
scope.reset = reset;
scope.fileChange = fileChange;
reset();
function reset() {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
if(typeof scope.onSubmitCallback === 'function') {
scope.onSubmitCallback();
}
}
function fileChange(file) {
if(typeof scope.onFileChange === 'function'){
scope.onFileChange(file);
}
}
}
}
})();
UploadFile.html
<form>
<div>
...
</div>
<input type="submit" ng-click="reset()" file-on-change="fileChange($files)" />Upload
</form>
parent.html
<file-uploader on-submit-callback="onUpload" on-file-change="onFileChange" ng-controller="UploadCtrl" />
upload.controller.js
...
$scope.onUpload = function() {
console.log('onUpload clicked %o', arguments);
};
$scope.onFileChange = function(e) {
var imageFile = (e.srcElement || e.target).files[0];
}
...

AngularJS binding issues and iteration loops

I have this factory:
.factory('Options', function () {
var getOptions = function () {
var storageData = sessionStorage.siteOptions;
if (storageData !== 'undefined')
return angular.fromJson(storageData);
return {
rotateBackground: false,
enableMetro: true
};
};
var saveOptions = function (options) {
sessionStorage.siteOptions = angular.toJson(options);
}
return {
get: getOptions,
save: saveOptions
};
});
which works fine on my profile page:
.controller('ProfileController', ['Options', function (options) {
var self = this;
self.options = options.get();
self.save = function () {
options.save(self.options);
}
}]);
The html looks like this:
<div class="row" ng-controller="ProfileController as profile">
<div class="large-4 columns">
<h2>Site options</h2>
<form name="optionsForm" ng-submit="profile.save()" role="form">
<div class="row">
<div class="large-12 columns">
<input id="enable-metro" type="checkbox" ng-model="profile.options.enableMetro"><label for="enable-metro">Enable metro design</label>
</div>
<div class="large-12 columns">
<input id="enable-background-rotate" type="checkbox" ng-model="profile.options.rotateBackground"><label for="enable-background-rotate">Enable rotating background</label>
</div>
<div class="large-12 columns">
<button class="button">Save</button>
</div>
</div>
</form>
</div>
</div>
But I have this other page that has a controller that needs to be aware if the options are ever saved. Basically, if saveOptions is ever called, then I need any page that looks at options to be notified.
The reason for this, is for example:
.controller('MetroController', ['Options', function (options) {
scope.options = options.get();
scope.$watch(function () {
return options.get();
}, function () {
scope.options = options.get();
});
}])
// ---
// DIRECTIVES.
// ---
.directive('metro', function () {
return {
restrict: 'A',
controller: 'MetroController',
controllerAs: 'metro',
link: function (scope, element, attr) {
scope.$watch(function () {
return metro.options.enableMetro;
}, function (enableMetro) {
if (enableMetro) {
element.addClass('metro');
} else {
element.removeClass('metro');
}
});
}
}
});
As you can see, this is trying to apply a class based on the enableMetro flag. But when I run this, I get an error about the amount of iterations this has had to loop through.
Can someone help me with this?
I think I have this solved.
I changed my options factory to this:
.factory('Options', function () {
var getOptions = function () {
var storageData = sessionStorage.siteOptions;
if (storageData !== 'undefined')
return angular.fromJson(storageData);
return {
rotateBackground: false,
enableMetro: true
};
};
var saveOptions = function (options) {
sessionStorage.siteOptions = angular.toJson(options);
current = getOptions();
}
var current = getOptions();
return {
current: current,
save: saveOptions
};
});
then in my controllers, I just did this:
.controller('MetroController', ['$scope', 'Options', function ($scope, options) {
var self = this;
self.options = options.current;
$scope.$watch(function () {
return options.current;
}, function () {
self.options = options.current;
});
}])
// ---
// DIRECTIVES.
// ---
.directive('metro', function () {
return {
restrict: 'A',
controller: 'MetroController',
link: function (scope, element, attr, controller) {
scope.$watch(function () {
return controller.options.enableMetro;
}, function (enableMetro) {
if (enableMetro) {
element.addClass('metro');
} else {
element.removeClass('metro');
}
});
}
}
});
and that seems to work fine.

Adding a new data model to Malhar-Angular-Dashboard

Im' working on the Malhar Angular Dashboard, based on this github project https://github.com/DataTorrent/malhar-angular-dashboard.
As per the documentation in the link post just above, under the 'dataModelType' heading 1/2 way down:
`The best way to provide data to a widget is to specify a dataModelType in the Widget Definition Object (above). This function is used as a constructor whenever a new widget is instantiated on the page.`
And when setting up the Widget Definition Objects, there are various options to choose from :
templateUrl - URL of template to use for widget content
template - String template (ignored if templateUrl is present)
directive - HTML-injectable directive name (eg. "ng-show")
So when I add my own widget definition column chart, I attempt to use the 'template' option; however it does NOT inject the {{value}} scope variable I'm setting.
Using the original datamodel sample widget def, it works fine using the 'directive' option. If I mimic this method on my column chart definition then it works ! But it doesn't work using the template option.
Here's the 'widgetDefinitions' factory code :
(function () {
'use strict';
angular.module('rage')
.factory('widgetDefinitions', ['RandomDataModel','GadgetDataModel', widgetDefinitions])
function widgetDefinitions(RandomDataModel, GadgetDataModel) {
return [
{
name: 'datamodel',
directive: 'wt-scope-watch',
dataAttrName: 'value',
dataModelType: RandomDataModel // GOTTA FIGURE THIS OUT !! -BM:
},
{
name: 'column chart',
title: 'Column Chart',
template: '<div>Chart Gadget Here {{value}}</div>',
dataAttrName: 'value',
size: {width: '40%',height: '200px'},
dataModelType: ColumnChartDataModel
},
];
}
})();
and here are the factories:
'use strict';
angular.module('rage')
.factory('TreeGridDataModel', function (WidgetDataModel, gadgetInitService) {
function TreeGridDataModel() {
}
TreeGridDataModel.prototype = Object.create(WidgetDataModel.prototype);
TreeGridDataModel.prototype.constructor = WidgetDataModel;
angular.extend(TreeGridDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
//this.treeGridOptions = {};
this.updateScope('THIS IS A TreeGridDataModel...'); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return TreeGridDataModel;
});
'use strict';
angular.module('rage')
.factory('ColumnChartDataModel', function (WidgetDataModel) {
function ColumnChartDataModel() {
}
ColumnChartDataModel.prototype = Object.create(WidgetDataModel.prototype);
ColumnChartDataModel.prototype.constructor = WidgetDataModel;
angular.extend(ColumnChartDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
var value = 'THIS IS A ColChartDataModel...';
//$scope.value = value;
this.updateScope(value); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return ColumnChartDataModel;
});
and finally the directives:
'use strict';
angular.module('rage')
.directive('wtTime', function ($interval) {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<div>Time<div class="alert alert-success">{{time}}</div></div>',
link: function (scope) {
function update() {
scope.time = new Date().toLocaleTimeString();
}
update();
var promise = $interval(update, 500);
scope.$on('$destroy', function () {
$interval.cancel(promise);
});
}
};
})
.directive('wtScopeWatch', function () {
return {
restrict: 'A',
replace: true,
template: '<div>Value<div class="alert alert-info">{{value}}</div></div>',
scope: {
value: '=value'
}
};
})
.directive('wtFluid', function () {
return {
restrict: 'A',
replace: true,
templateUrl: 'app/views/template2/fluid.html',
scope: true,
controller: function ($scope) {
$scope.$on('widgetResized', function (event, size) {
$scope.width = size.width || $scope.width;
$scope.height = size.height || $scope.height;
});
}
};
});
I'd like to know why ONLY the directive option will update the wigdet's data and not the template option.
thank you,
Bob
I believe I see the problem. The dataAttrName setting and updateScope method are actually doing something other than what you're expecting.
Look at the makeTemplateString function here. This is what ultimately builds your widget's template. You should notice that if you supply a template, the dataAttrName does not even get used.
Next, take a look at what updateScope does, and keep in mind that you can override this function in your own data model to do what you really want, a la:
angular.extend(TreeGridDataModel.prototype, {
init: function() {...},
destroy: function() {...},
updateScope: function(data) {
// I don't see this "main" object defined anywhere, I'm just going
// off your treegrid.html template, which has jqx-settings="main.treeGridOptions"
this.widgetScope.main = { treeGridOptions: data };
// Doing it without main, you could just do:
// this.widgetScope.treeGridOptions = data;
// And then update your treegrid.html file to be:
// <div id="treeGrid" jqx-tree-grid jqx-settings="treeGridOptions"></div>
}
});

angular directive encapsulating a delay for ng-change

I have a search input field with a requery function bound to the ng-change.
<input ng-model="search" ng-change="updateSearch()">
However this fires too quickly on every character. So I end up doing something like this alot:
$scope.updateSearch = function(){
$timeout.cancel(searchDelay);
searchDelay = $timeout(function(){
$scope.requery($scope.search);
},300);
}
So that the request is only made 300ms after the user has stopped typing. Is there any solution to wrap this in a directive?
As of angular 1.3 this is way easier to accomplish, using ngModelOptions:
<input ng-model="search" ng-change="updateSearch()" ng-model-options="{debounce:3000}">
Syntax: {debounce: Miliseconds}
To solve this problem, I created a directive called ngDelay.
ngDelay augments the behavior of ngChange to support the desired delayed behavior, which provides updates whenever the user is inactive, rather than on every keystroke. The trick was to use a child scope, and replace the value of ngChange to a function call that includes the timeout logic and executes the original expression on the parent scope. The second trick was to move any ngModel bindings to the parent scope, if present. These changes are all performed in the compile phase of the ngDelay directive.
Here's a fiddle which contains an example using ngDelay:
http://jsfiddle.net/ZfrTX/7/ (Written and edited by me, with help from mainguy and Ryan Q)
You can find this code on GitHub thanks to brentvatne. Thanks Brent!
For quick reference, here's the JavaScript for the ngDelay directive:
app.directive('ngDelay', ['$timeout', function ($timeout) {
return {
restrict: 'A',
scope: true,
compile: function (element, attributes) {
var expression = attributes['ngChange'];
if (!expression)
return;
var ngModel = attributes['ngModel'];
if (ngModel) attributes['ngModel'] = '$parent.' + ngModel;
attributes['ngChange'] = '$$delay.execute()';
return {
post: function (scope, element, attributes) {
scope.$$delay = {
expression: expression,
delay: scope.$eval(attributes['ngDelay']),
execute: function () {
var state = scope.$$delay;
state.then = Date.now();
$timeout(function () {
if (Date.now() - state.then >= state.delay)
scope.$parent.$eval(expression);
}, state.delay);
}
};
}
}
}
};
}]);
And if there are any TypeScript wonks, here's the TypeScript using the angular definitions from DefinitelyTyped:
components.directive('ngDelay', ['$timeout', ($timeout: ng.ITimeoutService) => {
var directive: ng.IDirective = {
restrict: 'A',
scope: true,
compile: (element: ng.IAugmentedJQuery, attributes: ng.IAttributes) => {
var expression = attributes['ngChange'];
if (!expression)
return;
var ngModel = attributes['ngModel'];
if (ngModel) attributes['ngModel'] = '$parent.' + ngModel;
attributes['ngChange'] = '$$delay.execute()';
return {
post: (scope: IDelayScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) => {
scope.$$delay = {
expression: <string>expression,
delay: <number>scope.$eval(attributes['ngDelay']),
execute: function () {
var state = scope.$$delay;
state.then = Date.now();
$timeout(function () {
if (Date.now() - state.then >= state.delay)
scope.$parent.$eval(expression);
}, state.delay);
}
};
}
}
}
};
return directive;
}]);
interface IDelayScope extends ng.IScope {
$$delay: IDelayState;
}
interface IDelayState {
delay: number;
expression: string;
execute(): void;
then?: number;
action?: ng.IPromise<any>;
}
This works perfectly for me: JSFiddle
var app = angular.module('app', []);
app.directive('delaySearch', function ($timeout) {
return {
restrict: 'EA',
template: ' <input ng-model="search" ng-change="modelChanged()">',
link: function ($scope, element, attrs) {
$scope.modelChanged = function () {
$timeout(function () {
if ($scope.lastSearch != $scope.search) {
if ($scope.delayedMethod) {
$scope.lastSearch = $scope.search;
$scope.delayedMethod({ search: $scope.search });
}
}
}, 300);
}
},
scope: {
delayedMethod:'&'
}
}
});
Using the directive
In your controller:
app.controller('ctrl', function ($scope,$timeout) {
$scope.requery = function (search) {
console.log(search);
}
});
In your view:
<div ng-app="app">
<div ng-controller="ctrl">
<delay-search delayed-method="requery(search)"></delay-search>
</div>
</div>
I know i'm late to the game but,hopefully this will help anyone still using 1.2.
Pre ng-model-options i found this worked for me, as ngchange will not fire when the value is invalid.
this is a slight variation on #doug's answer as it uses ngKeypress which doesn't care what state the model is in.
function delayChangeDirective($timeout) {
var directive = {
restrict: 'A',
priority: 10,
controller: delayChangeController,
controllerAs: "$ctrl",
scope: true,
compile: function compileHandler(element, attributes) {
var expression = attributes['ngKeypress'];
if (!expression)
return;
var ngModel = attributes['ngModel'];
if (ngModel) {
attributes['ngModel'] = '$parent.' + ngModel;
}
attributes['ngKeypress'] = '$$delay.execute()';
return {
post: postHandler,
};
function postHandler(scope, element, attributes) {
scope.$$delay = {
expression: expression,
delay: scope.$eval(attributes['ngKeypressDelay']),
execute: function () {
var state = scope.$$delay;
state.then = Date.now();
if (scope.promise) {
$timeout.cancel(scope.promise);
}
scope.promise = $timeout(function() {
delayedActionHandler(scope, state, expression);
scope.promise = null;
}, state.delay);
}
};
}
}
};
function delayedActionHandler(scope, state, expression) {
var now = Date.now();
if (now - state.then >= state.delay) {
scope.$parent.$eval(expression);
}
};
return directive;
};

Resources