A time for $timeout and ng-change

And now for another episode of AngularJS coding with your host, Eric Dorothy. (cue applause) Well it’s that time again. The time where I find myself creating an elegant solution to problem and sharing it with you. This problem starts with a choice between ng-change and ng-blur event handlers on an <input> control. Which one should I choose?

  • If I use ng-blur then no changes are recorded until my focus leaves the control.
  • If I use the ng-change then all changes are recorded as I make them.

If I’m performing some sort of client side validation there is not much of an issue between the two. However, what if I have to run to the server to do the validation? What if I’m using this to save my changes very much the way that Goggle does with the Gmail contacts? Then the number of server calls limits me to the ng-blur. Using ng-change would perform a server call for every character that I typed (or erased). I could write a directive that overlays the input box and call it something clever. I might do that in a future post, but for now I just want to create something simple to drive home the point of how to accomplish the task.
Ref Timeout
I’m going to make use of Angular’s $timeout service to help me achieve what I want. You can use this service to create a new promise that will be resolved when the timeout value has been reached. i.e. $timeout(fn, [delay], [invokeApply]); Since we get a promise back from this usage, we are not blocking the main ui thread. This is important because blocking for any amount of time can really mess up someone who is trying to type into this input. “Causing someone to make typo’s is bad, mmm-kay?” Now it’s time to look at some code.

Code Sample

I’ve already explained how ng-blur works, so I’m not going to spend time on it here. Instead I’m going to focus on making the ng-change a little more tolerant. We want to allow our users to type in the box, but when they have finished typing, we want to send the changed result to the server.

HTML

Here is the HTML code snippet that I’ve used to create my input box. Please take note that I’ve given it a place holder or watermark text and a max length. The $scope has an object called widget which has a name. This value is what is bound to the text box here. The change event method onWidgetNameChange() will be called whenever a character is typed in the input box.

<input type="text" placeholder="Not Saved" ng-model="widget.name" maxlength="50"
        ng-change="onWidgetNameChange()" />

JavaScript

The JavaScript file is where things get really interesting. For the most part this is your standard Angular file, but as you can see I’ve created a function called executeCommand that does something interesting. The logTime function is created so that we can see the time line of events. I’ve also output to the console log in the function I’ve passed to it, so that you can watch when the change actually happens. In the onWidgetNameChange method, I’ve passed on the reference to the timeout promise, the delay (in milliseconds), the action definition. Let’s see what happens…

angular.module('timeoutFunModule', [])
	.controller('timeoutController', ['$scope', '$timeout', 
                        function (scope, ngTimeout) {

	function executeCommand(promise, waitMs, action) {
	    if (promise)
	        ngTimeout.cancel(promise);

	    return ngTimeout(function() {
	        action() 
	    }, waitMs);
	};

    function logTime() {
		var date = new Date();		
		console.log('Last Change: ' + date.getSeconds() + '.' + date.getMilliseconds());
	};

	var ncPromise;
	scope.onWidgetNameChange = function () {
	    logTime();
		ncPromise = executeCommand(ncPromise, 500, function () {
				console.log('Changing the name of the widget here.');
				logTime();
			});
	};

	scope.widget = {
		id: 54,
		name: 'Sample Widget'
	};
}]);

Output

Now as you can see in the event handler, with the exception of the logging of the time, the only thing going on is the action definition and the setting of the promise variable returned by the executeCommand method. Here is the sample output that was produced by this.
Timeout.Output.Capture
As you can see from the output the difference between the last change at 10.272 and the actual changing of the widget’s name at 10.778 is 506 milliseconds. If we account that some minuscule amount of time was spent outputting the phrase Changing the name of the widget here then we are right on target with our timeout.

Conclusion

While this may not be the answer to all of your problems persisting data to the server; I hope that I’ve given you some insight into the Angular $timeout service. I make use of it here to limit the number of potential server calls here. How will you use it best?

Cheers!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s