Telerik blogs
KendoAngularCtrlAs_header

AngularJS is an extremely popular framework. Despite its widespread adoption, I come across many implementations that don't adhere to best practices for scale, testing, and performance. I am often surprised that developers aren't aware of newer capabilities that exist in Angular and JavaScript.

In this article, you will learn how to take advantage of JavaScript language features and Angular's built-in capabilities to create an architecture that minimizes overhead and optimizes performance for your apps while increasing both their testability and maintainability. I'll show you why the approaches commonly shared in popular blog posts have drawbacks and how to overcome them.

As a bonus you'll learn how well Kendo UI integrates with Angular through a hands-on implementation of its widgets in a functioning AngularJS app.

The Sample Application

Kendo UI and AngularJS create powerful synergy for building rich Single Page Applications (SPA). I demonstrated this in a recent session at Telerik's TelerikNEXT conference in Boston.

demoApp

You can run the live demo of the app I used to demonstrate how the two technologies work together. This demo is based on the open source Kendo UI Core.

The app itself is fairly straightforward. It accepts a weight, height, and birth date as input and then outputs the Basal Metabolic Rate (BMR) for males and females.

If you aren't familiar with BMR, it is simply an estimate of how many calories your body burns at rest. If you did nothing but sit on a couch all day and watch the television, you would likely still burn a few thousand calories. As long as you don't exceed that many calories in food intake, the theory is you won't gain weight. Add calories from other activities and you can guess your daily energy expenditure.

To simplify things, I packaged all code (HTML and JavaScript) in a single file that is available on GitHub.

Quick Angular Intro

If you're not familiar with Angular, I highly recommend you watch the beginning of my session because I do a more thorough job of introducing it. Although there is a lot to the framework, I will focus on a few key elements here.

An Angular "app" is often referred to as a "module" and is essentially the intersection of several different modules to create a container for components. Components can be pure JavaScript constructs. For example, in the sample project, the function to calculate someone's age based on their birthday is pure JavaScript:

function calculateAge(birthday) {
    var today = new Date();
    var nowyear = today.getFullYear();
    var nowmonth = today.getMonth();
    var nowday = today.getDate();
    var birthyear = birthday.getFullYear();
    var birthmonth = birthday.getMonth();
    var birthdate = birthday.getDate();
    var age = nowyear - birthyear;
    var agemonth = nowmonth - birthmonth;
    var ageday = nowday - birthdate;
    if (agemonth < 0 || (agemonth == 0 && ageday < 0)) {
        age = parseInt(age) - 1;
    }
    return age;
}

We'll avoid the philosophical discussions about single versus multiple vars or optimizing the inline calculations for now. It's a demo after all!

A special type of component in Angular is called a controller. You can think of controllers as view models or smart data containers. They are smart because they can be bound to the UI and respond to changes. The traditional way to create a controller is to give it something called $scope. Here is an example of a controller:

app.controller('myCtrl', function ($scope) {
    $scope.text = "Hello, world.";
});

There are more advanced ways to define this, but I'm sticking with the simple explanation for now. The controller has a property that you can now bind to the UI, like this:

<div ng-controller="myCtrl">{{text}}</div>

The div is "attached" to the controller. Once attached, it has access to the scope, and can therefore output the text property of the controller's scope. If you were to run the example, you would see a single div with the "Hello, world." text inside it.

mvc

Of course, the demo app I linked to doesn't just expose properties. It takes multiple inputs and outputs computed values. Somehow Angular "knows" when properties change. To see this, simply use one of the slide rules to change the weight, or key in a weight, or use the arrows in the Kendo widget to change it. Once the widget loses focus (i.e. you tab or mouse out) the other controls update and keep the values in sync. How is this possible?

Getting Dirty

The key to understanding how Angular manages data-binding really boils down to something called the digest loop and dirty checking. It is again well beyond the scope of this article to get into the gory details, but let me try to explain it at a high level.

Let's assume we have the simple controller I showed you earlier, and add a tag above it so that the HTML looks like this:

<div ng-controller="myCtrl">
    <input ng-model="text" placeholder="Start typing" type="text"/>
    <div>{{text}}</div>
</div>

You can run this interactively here.

Here's a very simplified overview of what happens:

  1. Angular registers the controller.

  2. Angular detects the controller declaration in the HTML (ng-controller), and creates the scope for it.

  3. Because the input is bound to text, Angular adds an event listener to the input field so it can update the model when the input changes.

  4. Because the div exposes the text property, Angular registers what is called a "watch" on the text property to know when it changes.

  5. Everything stops at this point.

  6. You press a key. This initiates a digest loop.

  7. The text property is updated with the new value.

  8. Angular iterates through it's list of watches. It finds a watch on the text property. It compares the current value with the last known value (this is referred to as dirty checking) and, because it changed, Angular will update the div element with the new value.

  9. Angular runs another digest loop because there were changes in the previous one. This time there are no changes detected, so it exits the digest loop.

  10. Go to 5.

OK, I used this as an excuse to revive the GOTO statement. You caught me!

$watch Out!

Now that you understand a bit about how the data-binding works, how can your code actively participate? For example, let's assume that for some insane reason you wanted to capture the text property every time it changed and pass it off to an API for logging purposes. How do you know in your code that the property changed?

The typical answer is to set up your own $watch. Here's a very basic implementation:

$scope.$watch("text", function (oldVal, newVal) {
    console.log("Updated " + oldVal + " to " + newVal);
});

If you run the updated code with your console open, you'll see the changes in real-time.

But here's the problem: in this little example, we just "double-watched" a single property. Angular is already watching it for changes because of data-binding, and now we are also watching it for our own code. That's fine for this little application, but those watches can grow out of hand on large applications and add a lot of overhead to every digest loop. Fortunately, there is a better way!

Enter the controller as Syntax

Going back to the original example, you may have noticed that I don't rely on scope too much. The truth is that I still use scope, but in a more advanced way.

One reason I take this approach is because I like the idea of building as much of my application in pure JavaScript as possible. This keeps me from getting too tied into the framework and makes it easier to prepare for change. I'm very confident this approach will, for example, make it easier to migrate existing applications to Angular 2.0 when it is released. Therefore, my controllers are defined as Plain Old JavaScript objects (POJOs) that I can test without Angular.

Take a look at the controller in the BMR example:

function Controller() {
   this.weight = 200;
   this.height = 70;
   this.birthday = new Date(1974, 8, 22);
}

It is a simple JavaScript object. In my HTML, I add a fragment to the controller declaration:

<div class="row" ng-controller="demoCtrl as ctrl">

This declares that the controller will be used as the scope, so it can perform the data-binding itself. It also provides an alias to the controller, and therefore I reference properties such as height like this:

<input kendo-numeric-text-box k-min="40" k-max="100" k-ng-model="ctrl.height" class="col-sm-3"/>

If you're wondering about the extra attributes, Angular has an awesome feature called directives that enable you to extend HTML with controls and behaviors. Kendo UI provides directives for all of the built-in widgets, so the attributes you see will automatically convert my input element into a full-blown numeric text box widget! Note the data-binding uses ctrl.height to specify the controller alias and the property.

OK, so now what? How can I watch something if I don't have a reference to scope?

Enter Modern JavaScript

With all the hype surrounding ECMAScript 2015 (also known as ECMAScript 6, ES6, or Harmony), I think many developers missed out on some now standard ECMAScript 5 features. A quick glance at this compatibility table shows that your ES5 code will work across all of the popular modern browsers, whether on desktops, slates, or phones. Let's see how we can use ES5 features to improve our code.

If you recall how the digest loop works, Angular already has a set of internal watches it uses to monitor properties that participate in data-binding. The "typical" approach for computed fields is to watch the two properties, or watch the method that performs the computation, and update the computed property. In the BMR example, this would involve watching height, weight, and birth day, then recomputing the BMR and updating the fields. This approach would look like this for the male BMR:

$scope.$watch(function () { return man(this.weight, this.height, calculateAge(this.birthday); },
    function (oldVal, newVal) {
        $scope.manBMR = newVal; 
    });

The watch expression computes the BMR and triggers if the computed BMR changes (notice we can evaluate a function instead of watching a specific property). Unfortunately, this also means there are two watches being fired: one for us to update the BMR, and a second for Angular to update the DOM when the BMR changes.

To improve this, we can use the ECMAScript 5 functionality to define a property and use its getters and setters. To better illustrate how this works, here is an example of triggering a digest with the $scope method:

  1. User updates age.

  2. Angular starts digest loop.

  3. Angular watches the BMR property, but it hasn't changed.

  4. You are watching the BMR calculation, and it has changed, so you update the BMR property.

  5. Angular starts another digest loop (it will keep performing digest loops until no properties have changed).

  6. Angular watches the BMR property, and it has changed, so Angular updates the DOM .

  7. You are watching the BMR calculation, but that hasn't changed.

  8. Angular starts another digest loop (remember, something changed on the last one).

  9. Because no other properties changed, Angular exits the digest loop.

Note this took three passes and evaluated two watches each time for the BMR (one for the computation, and one for the property).

Now instead of watching the computation, let's create a property that computes the values on the fly:

Object.defineProperty(Controller.prototype, "maleBmr", {
    enumerable: true,
    configurable: false,
    get: function() {
        return man(this.weight, this.height, calculateAge(this.birthday));
    }
});

Breaking open the digest loop, we find this:

  1. User updates age.

  2. Angular starts digest loop.

  3. Angular watches the BMR property, which in turn calculates the values and the values have changed, so Angular updates the DOM.

  4. Angular starts another digest loop.

  5. Because no other properties changed, Angular exits the digest loop.

Now there were just two passes, and only one watch was evaluted for the BMR.

Closing the (Digest) Loop

AngularJS and Kendo UI work very well together. It is common in complex UIs to have multiple properties that are interconnected and update based on dependent values. Instead of cluttering your application with unnecessary watches that can create performance problems down the road, consider using modern JavaScript and the controller as feature to simplify your code, keep it as close to pure JavaScript as possible, and avoid those extra digest loops. I've successfully written and participated on teams that have built large enterprise apps without using a single reference to $scope or explicit $watch.

If you learned something new, don't stop there. Take some extra time and read my series that covers The Top 5 Mistakes AngularJS Developers Make.

Happy coding!


jeremy likness
About the Author

Jeremy Likness

Jeremy is a senior cloud developer advocate for Azure at Microsoft and a former 8-year Microsoft MVP. Jeremy is an experienced entrepreneur and technology executive who has successfully helped ship commercial enterprise software for 20 years. You can read more from him at his blog or find him on GitHub.

Comments

Comments are disabled in preview mode.