Debounce an ng-change event in angular - angularjs

What's a good way to debounce an ng-change event in angular? I know about the debounce available in ng-model options, but I want my model to update right away as its changed because the user has the option to submit this data, and I don't want any information to be missing from the post because the model was waiting for the debounce. I do want to add a debouncer on my ng-change event or the function it calls. The html for my element
<div ng-change="saveProg()" text-angular ng-model="message" required></div>
I'd like to debounce the saveProg function so it doesn't run once for every character typed, but once every second or so. My function looks like this
$scope.saveProg = function() {
ProgressService.saveProg({
topic: $scope.topic,
message: $scope.message
})
}

Related

Angular checkbox update not running a full digest onclick to display template

How can I make checkbox run a full digest onclick, instead of onblur?
My problem is, the checkbox click only runs the local scope for the directive it is inside. I also have other directives that need to update onclick, instead of onblur.
I tried using ng-click and $scope.$apply() but I was getting $rootScope digest errors. After looking around a bit more I added $timeout around the $scope.$apply(). This fixed all my issues.
scope.ngclickApply = function(){
$timeout(function(){
scope.$apply();
},0,false)
}
and in the html
<label ng-click="ngClickApply()">
<input type="checkbox">{{label}}
</label>

How does AngularJS respond to a form value being updated in Javascript?

I don't use Angular regularly, but I understand that one of the key features is that when data is updated on a form element, it is automatically updated in the model.
If you are instead using a library like jQuery, you must manually attach an event to the form input that updates the model when it is changed, as in $('#myInput').on('change', updateModel);
Although the above handler will be fired when myInput is changed by the user, it will not be fired if myInput is changed by Javascript code such as $('#myInput').val('hello world');
My question is, how does Angular know when a form input is changed in Javascript code?
Angular applies a scope digest every time it's needed (by an Angular function) during which it checks the states of all the scope variables, including the models used, of course.
If you modify some of those variables manually, using JavaScript, jQuery, etc... Angular will not know that the changes have occured and you need to tell it so either by doing $scope.$apply() or by wrapping the code block in a $timeout callback (these are the most commonly used methods).
If you don't do it manually, you'd have to wait for some (if any) other Angular event to trigger the digest cycle, which is never good.
See this example, note how nothing happens when you just update the value, but you need to do it manually (ng-click does it) in order for DOM to update:
angular.module('app', [])
.controller('Ctrl', function($scope){
$scope.ourValue = 'Initial Value';
window.exposedFunc = function(v, digest) {
$scope.ourValue = v;
if (digest) {
$scope.$apply();
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
<button onclick="exposedFunc('First Button Value')">Update Value - No Digest</button>
<button onclick="exposedFunc('Second Button Value', true)">Update Value - Force Digest</button>
<button ng-click="">Force Digest only</button>
<p>{{ourValue}}</p>
</div>
Here's a super simple example of binding using keyup event. It should be enough to get you started on your projects:
var res = document.getElementById('r');
function handleChange(v) {
res.textContent = v;
}
<input onkeyup="handleChange(this.value)" type="text" value="Initial value" />
<p id="r">No binding yet</p>

AngularJS: revert form on modal cancel?

I'm trying to use AngularJS 1.3.0's new $rollbackViewValue() method in ngModelController to cancel changes to a form in a modal popup or persist them when I close the modal. I'm using BootstrapUI for the $modal service.
I think I'm on the right track, but there is something that isn't quite working properly:
In my controller, I've got:
$scope.updateCharge = function (charge) {
var scope = $scope.$new();
scope.charge = charge;
var modalInstance = $modal.open({
templateUrl: 'client/app/views/providers/charges/updateCharge.html',
scope: scope
});
modalInstance.result.then(function () {
scope.charge.$save({providerId: providerId, chargeId: charge.id});
});
};
In my template, I have the following:
<form name="form" novalidate ng-model-options="{updateOn: 'save', debounce: {'save': 0 }}" class="form-horizontal" role="form">
<div class="modal-body">
<div class="form-group">
<label class="col-sm-3 control-label" for="chargeName">Name</label>
<div class="col-sm-9">
<input type="text" class="form-control" required ng-model="charge.code" id="chargeName"/>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-disabled="form.$invalid" ng-click="form.$broadcast('save'); $close()">Update</button>
<button class="btn btn-warning" ng-click="form.$rollbackViewValue(); $dismiss('cancel')">Cancel</button>
</div>
</form>
Generally speaking, this seems to work. When I click on cancel, my changes are reverted. When I click on Update, the modal closes but I do not see my updates in the scope.charge object.
I would have expected that my scope.charge object would be updated prior to the modal closing.
Am I using the ng-model-options incorrectly?
If I add a separate 'Apply' button that only does a form.$broadcast('save'), I see my scope object properly updated. So I am presuming that my $close() is being called prior to the event being processed by the ng-model-options. How can I avoid this race condition?
You can to use the $rollbackViewValue() method to revert changes but I think that is not the intention.
$rollbackViewValue(); Cancel an update and reset the input element's
value to prevent an update to the $modelValue, which may be caused by
a pending debounced event or because the input is waiting for a some
future event.
If you have an input that uses ng-model-options to set up debounced
events or events such as blur you can have a situation where there is
a period when the $viewValue is out of synch with the ngModel's
$modelValue.
In this case, you can run into difficulties if you try to update the
ngModel's $modelValue programmatically before these debounced/future
events have resolved/occurred, because Angular's dirty checking
mechanism is not able to tell whether the model has actually changed
or not.
The $rollbackViewValue() method should be called before
programmatically changing the model of an input which may have such
events pending. This is important in order to make sure that the input
field will be updated with the new model value and any pending
operations are cancelled.
The normal use case is to copy the model, optionally to persist the model and, if all is ok, refresh the model.
_this = this;
this.edit = function() {
this.modelToEdit = angular.copy(this.originalModel);
}
this.save = function () {
service.save(modelToEdit).then(function(savedModel) {
_this.originalModel = savedModel;
});
}
Or you can backup the model and restore when cancel
_this = this;
this.edit = function() {
this.backupModel = angular.copy(originalModel);
}
this.cancel = function() {
this.originalModel = this.backupModel;
}
this.save = function() {
service.save(this.originalModel).then(function(data) {}, function(error) {
_this.originalModel = _this.backupModel;})
}
I can see a few problems with your code:
ngModelOptions.updatedOn is meant to be a DOM event, i.e. click, blur, mouseenter, mouseover, etc,
The form controller does NOT have a $broadcast method, so it's never emitting an event.
I think the fact that it sort of works is because there is not type="button" on the <button> so they are considered as "submit" inside the form. And the model is updated because of that.
I suggest you use a simplified version, and
remove the ng-model-options from the form.
add a type="submit" to the Update button, and remove the form.$broadcast
add a type="button" to the Cancel button.
Am not sure how it would work with the modal, but here's a plunkr with ng-if: http://plnkr.co/edit/m37Fd0NybpnfslNkvJnO
I did not end up using any of the options suggested as answers, because the reality is, angular 1x has no "proper" way of doing what I want. Angular uses 2way binding, and yes I can write fancy directives to make life easier but infact it just makes the html looks even more complicated.
I settled with the suggested way as per many threads on the forum and that is to use angular.copy and then use the cloned model in your html. When you submit changes, set the cloned model to the original model.
There was heaps of examples on here on how to use angular.copy. And it works well.

applying ng-blur and keydown (to capture enter key) on the same input element shows $apply already in progress error

I am developing an application in which there are rows one after other which contain input boxes.I have attached ng-blur and ng-keydown events to the input boxes.
On ng-keydown, i check if an enter was pressed then set focus to the next input box but as ng-blur is also attached to the input, it also fires when i try to set focus to the next input which throws $apply already in progress error on the console and hence the code on ng-blur fails to run.
Html:
<div class="main" ng-app ng-controller="appCtrl">
<input type="text" ng-repeat="name in names" ng-model="name" ng-blur="checkUpdate(name)" ng-keydown="keydownoperations(names,$event)"/>
</div>
<script src="http://code.jquery.com/jquery-2.1.1.js"></script>
JS:
function appCtrl($scope){
$scope.names = ['John','Smith','Ravi'];
$scope.checkUpdate = function(name){
console.log(name);
}
$scope.keydownoperations = function(names,event){
var $this = $(event.target);
if(event.which === 13) // Enter key
{
if($this.next().length) // if the adjacent sibling exists set focus
{
$this.next().focus();
}
}
}
}
Please see this fiddle http://jsfiddle.net/jitender60/qDZa9/4/ to get a clear view of my problem
The $scope.$apply() is called automatically by both ng-blur and ng-keydown.
The problem occurred because you trigger the focus event programmatically inside the keydown event handler. That will eventually trigger a blur event on a previously active element which in this case also has ng-blur on it.
Angular doesn't know if the blur event come from a user interaction or is triggered programmatically.
You could avoid the problem by defer the .focus() execution, may be inject a $timeout service and use it like this:
$timeout(function () {
$this.next().focus();
}, 0);
Hope this helps.

angularjs timepicker ng-change

I am using AngularJS TimePicker (ui.bootstrap.timepicker). I would like to fire an event when the timepicker is changed. I looked for an ng-change attribute, but did not find one.
My purpose is that I would like to save the changes that are made to the model when the time is changed. Right now I've used ng-model to set the model, but I cannot figure out how to notify the controller so it can perform the save (the model is serialized to localstorage). I would like to avoid the use of a "Save" button if possible.
On a related note, I would also like to do the same thing for ui.bootstrap.datepicker.
You could use $scope.$watch to listen for changes to the timepicker (or datepicker) and call whatever is needed in the Controller. Here is a plunker that is based on the bootstrap example but alerts whenever the time is changed. You should be able to apply a similar logic for datepicker as well.
You can use in script
<input type='text' class="form-control" ng-model="callMeTime.startHour" ng-change= "addTime()" id='datetimepicker1' placeholder="End Time" onkeydown="return false"/>
<script>
<script type="text/javascript">
$(function() {
$('#datetimepicker1').datetimepicker({
format : "HH:mm",
});
$('#datetimepicker1').datetimepicker().on('dp.change', function (event) {
var user_time_picker1 = $("#datetimepicker1");
user_time_picker1.val(this.value);
user_time_picker1.trigger('input');
});
});
</script>

Resources