The Big Changes to Pay Attention to in AngularJS 1.3

AngularJS has evolved quite rapidly. Its latest release, version 1.3, nicknamed "superluminal-nudge", ushers in plenty of exciting features and improvements.

The AngularJS GitHub source code repository contains a changelog that provides a brief, bulleted-list summary of all the changes that have occurred in the AngularJS project. However this article will only discuss the major changes in the newest stable release. We will delve into the new features and improvements in Angular 1.3 that, in my opinion, shouldn’t be ignored.

Data Binding Options

In previous versions of Angular, ng-model — although one of the most important directives of all — was not very flexible. In Angular 1.3, it’s now possible to control exactly when to update bound variables, how long to wait before processing the update and more.

For example, to update a model when the relevant input field loses focus, the updateOn option can be overridden as follows:

<input ng-model-options="{updateOn: 'blur'}">

Another data binding option that’s useful is debounce. A couple of design patterns often employed in real-time search UIs is auto-completion and live filter. When using these design patterns, it’s a good idea to wait for the user to pause from typing before suggesting results in order to avoid unnecessary model updates and also to reduce distraction while users are still composing their search queries. Something similar can be implemented by setting the debounce option:

<input ng-model-options="{debounce: 500}">

In the above example, there will be a 0.5-second delay before the model is updated again.

Use the debounce option with care though. Since the model is not updated unless the specified time is completed, ng-click handlers used in processing form data may not work as you would expect. Instead of using ng-click, use ng-submit. Another workaround is to send an object to ng-model-options to make the update trigger debounce, but update immediately on blur, like below:

<input ng-model-options="{debounce: {'default': 300, 'blur': 0}}">

With ng-model-options, it’s also possible to do one-time data binding. This may be an option that can ensure performance gains if used properly.

Binding with a Getter/Setter

Angular now has the ability to bind to a getter/setter function. Instead of the model being an attribute on an object, it can now be a function. The function takes a single argument, which when defined should make the function behave like a setter. Calling the function without the argument defined will make it behave like a getter.

HTML

<input ng-model="value" ng-model-options="{getterSetter: true}">
<br />
Bound value: <span ng-bind="value()">

JavaScript

var _value;

scope.value = function (v) {
  if(angular.isDefined(v)) {
    _value = v;
  }
  return _value;
}

Rollback of Input Values

It’s often desirable to reset an input field’s value back to its previous value. For example, in many cases, a veteran web-application user’s natural instinct is to press the Esc key on their keyboard to go back to the previous value they entered. In Angular 1.3, you can facilitate this type of interaction using the $rollbackViewValue() method to revert the input field’s value. For example, to bind the described behavior to the Esc key, you can do the following:

HTML

<input id="inputText" name="inputText" ng-model="inputText" ng-model-options="{updateOn: 'blur'} ng-keyup="cancelEntry(formName.inputText, $event)"/>

JavaScript

$scope.cancelEntry = function (control, event) {
  // key code for the Esc key = 27
  if (event.keyCode === 27) {
    control.$rollbackViewValue();
  }
};

It’s also possible to extend this idea by binding the behavior to a "Reset" button that resets all the input fields in a web form to their previous values:

<form ng-submit="ctrl.submitAction()" name="formName" ng-model-options="{updateOn: 'submit'}">
  <input type="text" ng-model="ctrl.firstName" name="firstName">
  <input type="text" ng-model="ctrl.lastName" name="lastName">
  <button class="submit" ng-click="formName.$rollbackViewValue()">Reset</button>
</form>

Please note that in the above example, ng-model-options at form level is important because the input values are updated with the model while the user is changing their data, and so $rollbackViewValue() will not have any effect otherwise.

Submission Detection

A new form property has been introduced: $submitted. This new property determines if the user has attempted to submit the form, even if the submission was invalid.

With the $submitted property, it’s now possible to show in-line error messages inside a web form in a more "Angular" way.

Here’s an example of conditionally displaying a message if the surveyForm web form has already been submitted:

<div ng-if="surveyForm.$submitted">
  Form Submitted
</div>

It’s worth mentioning that $submitted becoming true doesn’t mean the data of this form has reached the server. That still has to happen inside the ng-submit handler.

Focus Detection

The name of the new $touched property might lead you to believe that it’s related to gestures on a touchscreen device. However, similar to $dirty, $touched is a property that determines if the user has already focused on an element and then unfocused it, with or without making any changes.

Validating Inputs

Validating the standard set of HTML5 input fields is now easier than ever because of improved support within Angular. Angular will populate a special property called $error for each field containing the states of validation.

To accept email addresses, for example, you can use HTML5’s email input type like so:

Example

<form name="form">
  <input type="email" id="inputEmail" name="email" placeholder="Your Email" ng-model="email" required>
  <div class="has-error" ng-if="form.email.$dirty">
    <span ng-show="form.email.$error.required">Email is required.</span>
    <span ng-show="form.email.$error.email">Email address is invalid.</span>
  </div>
</form>

For more advanced needs, Angular now provides a much easier way to write custom validators using the $validators pipeline.

If, for instance, you want to validate an input such that it can only contain four lowercase alphabets followed by two numbers (e.g. "abcd12") all you will need to do is create a regular expression. The matching regular expression in this case is:

/^[a-z]{4}[0-9]{2}$/

We can use the regular expression above in conjunction with $validators to validate the input, like so:

HTML

<input type="text" placeholder="Example format: abcd12" ng-model="customerId" customer-id-validator />

JavaScript

angular.module('myApp').directive('customerIdValidator', function() {
  return {
    require: 'ngModel',
    link: function($scope, element, attrs, ngModel) {
      ngModel.$validators.customerId = function(modelValue, viewValue) {
        var value = modelValue || viewValue;
        return /^[a-z]{4}[0-9]{2}$/.test(value);
      }
    }
  }
});

Asynchronous Validation

Angular 1.3 introduced asynchronous validation which makes it possible for the validation logic to stay on the backend.

Implementation of an asynchronous validator is similar to that of a synchronous validator, with a small difference: Instead of returning a boolean value, an asynchronous validator returns a promise.

Asynchronous validators are defined on $asyncValidators.

Date and Time Inputs

The new HTML5 input types related to date and time have enhanced support in Angular 1.3. These input types are:

  • <input type="date">
  • <input type=" time">
  • <input type="datetime-local">
  • <input type="week">
  • <input type="month">

Any scope variable bound to these input fields can be assigned JavaScript Date objects. Setting invalid values will cause the error to be logged in the developer console, and the date field to be rendered as they would, ideally when given with invalid values, sometimes just mm/dd/yyyy.

Easier Inline Hints and Error Messages

Angular 1.3 makes it easier to display inline messages with two new directives, ng-messages and ng-message. To use them, angular-messages.js must be included in your application. With these directives, the markup for displaying inline hints and error messages can be simplified a lot.

In the following example, ng-messages, ng-message and ng-messages-multiple is used to conditionally display multiple error messages depending on the results of the form’s validation:

<input class="form-control"
  type="email"
  id="inputEmail"
  name="inputEmail"
  placeholder="Your Email"
  ng-model="ctrl.obj.instructorEmail"
  required />
<div class="has-error"
  ng-if="formName.inputEmail.$dirty"
  ng-messages="formName.inputEmail.$"
  ng-messages-multiple>
  <span ng-message="required">
    Email is required.
  </span>
  <span ng-message="email">
    Valid email address required.
  </span>
</div>

One way to make things even easier is to define a template with these messages and reuse them throughout the application where necessary.

Change in Controllers

AngularJS 1.3 introduced a breaking change: Controllers may no longer be defined from globally declared functions by default.

This restriction can be overridden by calling a simple configuration function:

angular.module('app').config(function($controllerProvider) {
	$controllerProvider.allowGlobals();
}

Updating URL Parameters

It is often necessary to update the URL the user can see in the browser’s address bar in reaction to a user-action. A new method, $route.updateParams(), allows the URL to be updated with a new set of parameters.

Canceling Route Change

Calling preventDefault() in an event object generated by the $routeChangeStart event will prevent the browser from navigating away from the current route. This is useful in preventing a user from accidentally moving to another view without performing some important task first, often used in apps where the user must discard or save changes before leaving the page.

Example

$rootScope.$on('$routeChangeStart', function(event, next, current) {
  if(current.id.indexOf('edit') !== -1 && scope.unsavedChanges) {
    event.preventDefault();
    $scope.msgText = "Please your save changes before leaving the page.";
    $scope.msgShow = true;
  }
});

Prefetch Your Templates

$templateService allows AngularJS applications to prefetch templates from the server before they need to be used. For a seamless user experience it’s often desirable to cache templates before users navigate to the corresponding views. This is especially useful for large templates.

Moreover, this service enables the possibility of building an offline-capable web app by fetching all the necessary templates while the user is still connected to the Internet.

A template may be requested through a simple function call as follows:

$templateRequest('detailsPage.html');

Scroll to an Anchor

AngularJS now provides a convenience service, $anchorScroll, that checks the current value of $location.hash() and then scrolls to the appropriate anchor. This is useful since, by default, the browser only scrolls to an anchor when the hash of the URL changes, not when a link is clicked on. This is often an annoyance when the user clicks on a link to scroll to an anchor, then scrolls away from the region and tries to click the link again to return, which, unless handled explicitly, will not work.

Preserve Trailing Slashes

Resources in previous versions of Angular treated trailing slashes as an optional element of URLs. In Angular 1.3, it’s now possible to keep trailing slashes by setting $resourceProvider.defaults.stripTrailingSlashes to false.

Example

angular.module('myApp').config(['$resourceProvider', function($resourceProvider) {
  // Do not strip trailing slashes from calculated URLs$resourceProvider.defaults.stripTrailingSlashes = false;
}]);

Limit Arrays, Strings and Numbers to a Specified Length

A nifty filter was added to Angular 1.3: limitTo. The filter does what the name implies: Given an array, string or number, limitTo: n will create an array containing only the first n or last n elements, depending on whether n is positive or negative, respectively.

Watch Variables in a Group

To watch for changes on a group of variables and then call a single function when a member of the group has changed, you can use the $watchGroup method.

Example

$scope.$watchGroup(['someVar', 'someOtherVar'], function () { 
  // Do something
});

Changes to Directives

Directives in Angular 1.3 has also witnessed major changes. One of the most important change to be aware of is that directives’ restrict property now defaults to EA, instead of just A.

Two new directives have been introduced: ng-true-value and ng-false-value, filling a critical gap in building forms that have checkboxes. Now, it’s possible to define what value to set to the model depending on whether checkboxes are checked or unchecked.

Example

<input type="checkbox" ng-model="chkValue" ng-true-value="'YES'" ng-false-value="'NO'">

Animation Behavior Changes

Angular 1.3 will wait until the following digest cycle to apply animations, with the aim of improving performance. This has the advantage of allowing your script to add and remove classes as necessary, and then apply the animation only on the next digest cycle, preventing redundant animation, and acting only on the net change of classes.

However, this means that the digest cycle must be invoked manually at times, for example, when animations are applied outside of the digest cycle.

Below is an example of how we can create a simple animation of changing the background of a table row slowly when a user clicks on it.

CSS

.selectable-table {
   cursor: pointer;
}

.selectable-table tr.selected {
   background-color: #ccc;
}

.selectable-table tr.selected-add,
.selectable-table tr.selected-remove {
   transition: all 0.5s linear;
}

JavaScript

angular.module("myApp").directive("selectable", function ($animate) {
  return function(scope, element, attrs) {
    element.on("click", function() {
      if(element.hasClass("selected")) {
        $animate.removeClass(element, "selected");
      } else {
        $animate.addClass(element, "selected");
      }
      // The following is needed because
      // this click event is happening outside
      // of Angular's digest cycles
      scope.$digest(); 
    })
  }
})

Callbacks can do everything that a promise does, but can quickly spin out of hand as the logic gets more complicated. The animation service, hence, has started using promises instead of callbacks to provide a much simpler programming interface.

Consider an example where a user clicks to select a table row. In addition to the animation of changing the background of row, we want to make the details section visible. This can be done as such:

$animate.addClass(element, "selected")
.then(function() {
	scope.$apply(function() {
    scope.showDetails = true;
  });
})

Web Accessibility Improvements

Angular 1.3 has introduced a module, ngAria, that makes it easier to make your web applications WAI-ARIA-compliant. WAI-ARIA is a set of web standards and specifications that make sites and apps more accessible for people with disabilities.

In most scenarios, the implementation simply involves including ngAria in your app’s list of dependencies.

ngAria supports a range of common AngularJS directives that makes HTML elements easier to understand when accessed with screen readers and similar assistive technologies. Supported directives include:

  • ngModel
  • ngShow
  • ngHide
  • ngDisabled
  • ngClick
  • ngDblclick
  • ngMessages

Internet Explorer Support

Angular 1.3 no longer supports Internet Explorer 8 and below. This decision by the AngularJS team makes sense. Microsoft has discontinued their support for Windows XP, and every officially supported version of Windows now allows its users to run Internet Explorer 9 and above. Moreover, this allows the AngularJS team to enhance the framework in terms of performance.

Conclusion

AngularJS version 1.3 is a must-have upgrade for your application, not just because you can do more with it, but because it’s three to four times faster with respect to DOM manipulation and digests, which translates to faster and more responsive applications. There are some smaller new features that are not discussed in this article, like the ngTransclude directive and improvements in testability. At the very least, I hope what I have discussed here helps you get a general idea regarding the direction AngularJS is headed towards.

Related Content

About the Author

Avinash Kaza is a senior developer at Toptal. He’s an experienced product development expert, having coached teams towards better collaboration while figuring out the best solutions to difficult problems.