Make Your Own AngularJS, Part 1: Scopes And Digest

Posted on by Tero Parviainen

Update: "Build Your Own AngularJS" is now available as an early-access eBook.

Angular is a mature and powerful JavaScript framework. It is also a large framework with many new concepts one needs to grasp before becoming truly effective. As web developers flock to Angular, many of them face the same hurdles. What exactly does the digest do? What are all the different approaches to defining a directive? What's the difference between a service and a provider?

While the Angular documentation is getting very good, and there's an increasing number of third-party resources, there's really no better way to really learn a technology than to take it apart and see what makes it tick.

In this article series, I'm going to build an implementation of AngularJS from the ground up. As I take you through it step by step, you'll gain a deep understanding of exactly how Angular works.

In this first installment of the series we'll see how Angular scopes work and what things like $eval, $digest, and $apply actually do. The dirty-checking logic of Angular may seem somewhat magical, but it really isn't, as you'll see.

Setup

You'll find the full source code for the project on GitHub. Instead of just cloning it though, I encourage you to build your own implementation bit by bit, and explore the code on each step by poking it from different directions. I have embedded JSBins throughout the text so you can interact with the code without ever leaving this page.

We're going to use the Lo-Dash library for some of the lower-level operations on arrays and objects. Angular itself does not use Lo-Dash, but for our purposes it makes sense to eliminate as much low-level boilerplate as we can. Whenever you see a reference to the underscore (_) object in the code, that's when we're calling a function provided by Lo-Dash.

We're also going to use the console.assert function to do some ad-hoc testing. The function should be available on all modern JavaScript environments.

Here's Lo-Dash and the assert function in action:

JS Bin

Scope Objects

Angular scopes are plain old JavaScript objects, on which you can attach properties just like you would on any other object. Scopes are created using the Scope constructor. Let's make the simplest possible version of it:

function Scope() {
}

Now we can use the new operator to make a scope object. We can also attach properties on it:

var aScope = new Scope();
aScope.firstName = 'Jane';
aScope.lastName = 'Smith';

There's nothing special about these properties. There are no special setters you need to call, nor restrictions on what values you assign. Where the magic happens instead is in two very special functions: $watch and $digest.

Watching Object Properties: $watch And $digest

$watch and $digest are two sides of the same coin. Together they form the core of what Angular scopes are all about: Reacting to changes in data.

With $watch you can attach a watcher to a scope. A watcher is something that is notified when a change occurs in the scope. You create a watcher by providing two functions to $watch:

  • A watch function, which specifies the piece of data you're interested in.
  • A listener function which will be called whenever that data changes.

As an Angular user, you actually usually specify a watch *expression* instead of a watch function. A watch expression is a string, like "user.firstName", that you specify in a data binding, a directive attribute, or in JavaScript code. It is parsed and compiled into a watch function by Angular. We will look at how this happens in a later article. In this article we'll use the slightly lower-level approach of providing watch functions directly.

In order to implement $watch, we need to have storage for all the watches that will be registered. Let's add an array for them in the Scope constructor:

function Scope() {
  this.$$watchers = [];
}

The double-dollar prefix $$ signifies that this variable should be considered private to the Angular framework, and should not be called from application code.

Now we can define the $watch function. It'll take the two functions as arguments, and store them in the $$watchers array. We want every scope to have that function, so let's add it to the prototype of the Scope function:

Scope.prototype.$watch = function(watchFn, listenerFn) {
  var watcher = {
    watchFn: watchFn,
    listenerFn: listenerFn
  };
  this.$$watchers.push(watcher);
};

The other side of the coin is the $digest function. It runs all the watches that have been registered on the scope. Let's define a very simple version of it, which just iterates over the watches and calls their listener functions:

Scope.prototype.$digest = function() {
  _.forEach(this.$$watchers, function(watch) {
    watch.listenerFn();
  });  
};

We can now attach watches and then run $digest, causing the listener functions to be called:

JS Bin

This isn't very useful in itself, of course. What we really want is to check if the values specified by the watch functions actually changed, and only then call the listener functions.

Checking For Dirty Values

As described above, the watch function of a watcher should return the piece of data whose changes we are interested in. Usually that piece of data is something that exists on the scope. To make accessing the scope more convenient, the watch function is called with the current scope as an argument. A watch function that's interested in a firstName attribute on the scope would then look like this:

function(scope) {
  return scope.firstName;
}

This is the general form that watch functions often take: Pluck some value from the scope and return it.

The $digest function's job is to call this watch function and compare its return value to whatever the same function returned last time. If the values differ, there watcher is dirty and its listener function should be called.

To make this work, $digest has to remember what the last value of each watch function was. Since we already have an object for each watcher, we can conveniently store the last value there. Here's a new definition of $digest that checks for value changes for each watch function:

Scope.prototype.$digest = function() {
  var self = this;
  _.forEach(this.$$watchers, function(watch) {
    var newValue = watch.watchFn(self);
    var oldValue = watch.last;
    if (newValue !== oldValue) {
      watch.listenerFn(newValue, oldValue, self);
      watch.last = newValue;
    }
  });  
};

For each watcher, we call the watch function, giving it the scope itself as an argument. We then compare the return value to what we've previously stored in the last attribute. If the values differ, we call the listener. For convenience, we hand the listener both the new and old values as well as the scope as arguments. Finally, we set the last attribute of the watcher to the new return value, so we'll be able to compare to that next time.

With this implementation we can see how listeners are being run when we call $digest:

JS Bin

We've now implemented the essence of Angular scopes: Attaching watches and running them in a digest.

We can also already see a couple of important performance characteristics that Angular scopes have:

  • Attaching data to a scope does not by itself have an impact on performance. If no watcher is watching a property, it doesn't matter if it's on the scope or not. Angular does not iterate over the properties of a scope. It iterates over the watches.
  • Every watch function is called during every $digest. For this reason, it's a good idea to pay attention to the number of watches you have, as well as the performance of each individual watch function or expression.

Getting Notified Of Digests

If you would like to be notified whenever an Angular scope is digested, you can make use of the fact that each watch is executed for each digest. Just register a watch without a listener function.

To support this use case, we need to check if the listener is omitted in $watch, and if so, put an empty no-op function in its place:

Scope.prototype.$watch = function(watchFn, listenerFn) {
  var watcher = {
    watchFn: watchFn,
    listenerFn: listenerFn || function() { }
  };
  this.$$watchers.push(watcher);
};

If you use this pattern, do keep in mind that Angular will look at the return value of watchFn even when there is no listenerFn. If you return a value, that value is subject to dirty-checking. To make sure your usage of this pattern doesn't cause extra work, just don't return anything. In that case the value of the watch will be constantly undefined.

JS Bin

The core of the implementation is now there, but we're still far from done. For instance, there's a fairly typical scenario we're not supporting yet: The listener functions themselves may also change properties on the scope. If this happens, and there's another watcher looking at the property that just changed, it might not notice the change during the same digest pass:

JS Bin

Let's fix this.

Keep Digesting While Dirty

We need to change the digest so that it keeps iterating over all watches until the watched values stop changing.

First, let's rename our current $digest function to $$digestOnce, and adjust it so that it runs all the watches once, and returns a boolean value that determines whether there were any changes or not:

Scope.prototype.$$digestOnce = function() {
  var self  = this;
  var dirty;
  _.forEach(this.$$watchers, function(watch) {
    var newValue = watch.watchFn(self);
    var oldValue = watch.last;
    if (newValue !== oldValue) {
      watch.listenerFn(newValue, oldValue, self);
      dirty = true;
      watch.last = newValue;
    }
  });
  return dirty;
};

Then, let's redefine $digest so that it runs the "outer loop", calling $$digestOnce as long as changes are happening:

Scope.prototype.$digest = function() {
  var dirty;
  do {
    dirty = this.$$digestOnce();
  } while (dirty);
};

$digest now runs all watches at least once. If, on the first pass, any of the watched values has changed, the pass is marked dirty, and all watches are run for a second time. This goes on until there's a full pass where none of the watched values has changed and the situation is deemed stable.

Angular scopes don't actually have a function called $$digestOnce. Instead, the digest loops are all nested within $digest. Our goal is clarity over performance, so for our purposes it makes sense to extract the inner loop to a function.

Here's the new implementation in action:

JS Bin

We can now make another important realization about Angular watches: They may be run many times per each digest pass. This is why people often say watches should be idempotent: A watch should have no side effects, or only side effects that can happen any number of times. If, for example, a watch function fires an Ajax request, there are no guarantees about how many requests your app is making.

In our current implementation there's one glaring omission: What happens if there are two watches looking at changes made by each other? That is, what if the state never stabilizes? Such a situation is shown in the code below. In the example the $digest call is commented out. Uncomment it to see what happens:

JS Bin

JSBin stops the function execution after a while (on my machine it manages to run for 100,000 iterations or so). If you execute the code on something like Node.js, it'll keep going forever.

Giving Up On An Unstable Digest

What we need to do is keep running the digest for some acceptable amount of iterations. If the scope is still changing after those iterations we have to throw our hands up and declare it's probably never going to stabilize. At that point we might as well throw an exception, since whatever the state of the scope is it's unlikely to be what the user was aiming for.

This maximum amount of iterations is called the TTL (short for Time To Live). By default it is set to 10. The number may seem small (we just ran the digest 100,000 times!), but bear in mind this is a performance sensitive area since digests happen often and each digest runs all watch functions. It's also unlikely that a user will have more than 10 watches chained back-to-back.

It is actually possible to adjust the TTL in Angular. We will return to this in a later article when we discuss providers and dependency injection.

Let's go ahead and add a loop counter to the outer digest loop. If it reaches the TTL, we'll throw an exception:

Scope.prototype.$digest = function() {
  var ttl = 10;
  var dirty;
  do {
    dirty = this.$$digestOnce();
    if (dirty && !(ttl--)) {
      throw "10 digest iterations reached";
    }
  } while (dirty);
};

This updated version causes our interdependent watch example to throw an exception:

JS Bin

This should keep the digest from running off on us.

Now, let's turn our attention to how we're actually detecting that something has changed.

Value-Based Dirty-Checking

For now we've been comparing the old value to the new with the strict equality operator ===. This is fine in most cases, as it detects changes to all primitives (numbers, strings, etc.) and also detects when an object or an array changes to a new one. But there is also another way Angular can detect changes, and that's detecting when something inside an object or an array changes. That is, you can watch for changes in value, not just in reference.

This kind of dirty-checking is activated by providing a third, optional boolean flag to the $watch function. When the flag is true, value-based checking is used. Let's redefine $watch to take this flag and store it in the watcher:

Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) {
  var watcher = {
    watchFn: watchFn,
    listenerFn: listenerFn,
    valueEq: !!valueEq
  };
  this.$$watchers.push(watcher);
};

All we do is add the flag to the watcher, coercing it to a true boolean by negating it twice. When a user calls $watch without a third argument, valueEq will be undefined, which becomes false in the watcher object.

Value-based dirty-checking implies that we have to iterate through everything contained in the old and new values if they are objects or arrays. If there's any difference in the two values, the watcher is dirty. If the value has other objects or arrays nested within, those will also be recursively compared by value.

Angular ships with its own equal checking function, but we're going to use the one provided by Lo-Dash instead. Let's define a new function that takes two values and the boolean flag, and compares the values accordingly:

Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) {
  if (valueEq) {
    return _.isEqual(newValue, oldValue);
  } else {
    return newValue === oldValue;
  }
};

In order to notice changes in value, we also need to change the way we store the old value for each watcher. It isn't enough to just store a reference to the current value, because any changes made within that value will also be applied to the reference we're holding. We would never notice any changes since essentially $$areEqual would always get two references to the same value. For this reason we need to make a deep copy of the value and store that instead.

Just like with the equality check, Angular ships with its own deep copying function, but we'll be using the one that comes with Lo-Dash. Let's update $digestOnce so that it uses the new $$areEqual function, and also copies the last reference if needed:

Scope.prototype.$$digestOnce = function() {
  var self  = this;
  var dirty;
  _.forEach(this.$$watchers, function(watch) {
    var newValue = watch.watchFn(self);
    var oldValue = watch.last;
    if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) {
      watch.listenerFn(newValue, oldValue, self);
      dirty = true;
      watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue);
    }
  });
  return dirty;
};

Now we can see the difference between the two kinds of dirty-checking:

JS Bin

Checking by value is obviously a more involved operation than just checking a reference. Sometimes a lot more involved. Walking a nested data structure takes time, and holding a deep copy of it also takes up memory. That's why Angular does not do value-based dirty checking by default. You need to explicitly set the flag to enable it.

There's also a third dirty-checking mechanism Angular provides: Collection watching. Just like value-based checking, it notices changes within objects and arrays. Unlike value-based checking, it does a shallow check and does not recurse onto deeper levels. This makes it more performant than value-based checking. Collection watching is available via the `$watchCollection` function. We'll take a look at how it's implemented later in the series.

Before we're done with value comparison, there's one more JavaScript quirk we need to handle.

NaNs

In JavaScript, NaN (Not-a-Number) is not equal to itself. This may sound strange, and that's because it is. If we don't explicitly handle NaN in our dirty-checking function, a watch that has NaN as a value will always be dirty.

For value-based dirty-checking this case is already handled for us by the Lo-Dash isEqual function. For reference-based checking we need to handle it ourselves. Let's do that by tweaking the $$areEqual function:

Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) {
  if (valueEq) {
    return _.isEqual(newValue, oldValue);
  } else {
    return newValue === oldValue ||
      (typeof newValue === 'number' && typeof oldValue === 'number' &&
       isNaN(newValue) && isNaN(oldValue));
  }
};

Now the watch behaves as expected with NaNs as well:

JS Bin

With the value-checking implementation in place, let's now switch our focus to the ways in which you can interact with a scope from application code.

$eval - Evaluating Code In The Context of A Scope

There are a few ways in which Angular lets you execute some code in the context of a scope. The simplest of these is $eval. It takes a function as an argument and all it does is immediately execute that function giving it the scope itself as an argument. It then returns whatever the function returned. $eval also takes an optional second argument, which it just passes as-is to the function.

The implementation of $eval is straightforward:

Scope.prototype.$eval = function(expr, locals) {
  return expr(this, locals);
};

Using $eval is also just as straightforward:

JS Bin

So what is the purpose of such a seemingly roundabout way to invoke a function? One could argue that $eval makes it just slightly more explicit that some piece of code is dealing specifically with the contents of a scope. $eval is also a building block for $apply, which is what we'll be looking at next.

However, probably the most interesting use of $eval only comes when we start discussing expressions instead of raw functions. Just like with $watch, you can give $eval a string expression. It will compile that expression and execute it within the context of the scope. We will implement this later in the series.

$apply - Integrating External Code With The Digest Cycle

Perhaps the best known of all functions on Scope is $apply. It is touted as the standard way to integrate external libraries to Angular. There's a good reason for this.

$apply takes a function as an argument. It executes that function using $eval, and then kick-starts the digest cycle by invoking $digest. Here is a simple implementation:

Scope.prototype.$apply = function(expr) {
  try {
    return this.$eval(expr);
  } finally {
    this.$digest();
  }
};

The $digest call is done in a finally block to make sure the digest will happen even if the function throws an exception.

The big idea of $apply is that we can execute some code that isn't aware of Angular. That code may still change things on the scope, and $apply makes sure that any watches on the scope will pick up on those changes. When people talk about integrating code to the "Angular lifecycle" using $apply, this is essentially what they mean. There really isn't much more to it than that.

Here's $apply in action:

JS Bin

Deferred Execution - $evalAsync

In JavaScript it's very common to execute a piece of code "later" – to defer its execution to some point in the future when the current execution context has finished. The usual way to do this is by calling setTimeout() with a zero (or very small) delay parameter.

This pattern applies to Angular applications as well, though the preferred way to do it is by using the $timeout service that, among other things, integrates the delayed function to the digest lifecycle with $apply.

But there is also another way to defer code in Angular, and that's the $evalAsync function on Scope. $evalAsync takes a function and schedules it to run later but still during the ongoing digest or before the next digest. You can, for example, defer some code from within a watch listener function, knowing that while that code is deferred, it'll still be invoked within the current digest iteration.

The first thing we need is a way to store the $evalAsync jobs that have been scheduled. We can do this with an array, which we initialize in the Scope constructor:

function Scope() {
  this.$$watchers = [];
  this.$$asyncQueue = [];
}

Let's next define $evalAsync, so that it adds the function to execute on this queue:

Scope.prototype.$evalAsync = function(expr) {
  this.$$asyncQueue.push({scope: this, expression: expr});
};

The reason we explicitly set the current scope in the queued object is to do with scope inheritance, which we'll discuss in the next article in this series.

Then, the first thing we will now do in $digest is to consume everything from this queue and invoke all the deferred functions using $eval:

Scope.prototype.$digest = function() {
  var ttl = 10;
  var dirty;
  do {
    while (this.$$asyncQueue.length) {
      var asyncTask = this.$$asyncQueue.shift();
      this.$eval(asyncTask.expression);
    }
    dirty = this.$$digestOnce();
    if (dirty && !(ttl--)) {
      throw "10 digest iterations reached";
    }
  } while (dirty);
};

The implementation guarantees that if you defer a function while the scope is still dirty, the function will be invoked later but still during the same digest.

Here's an example of how $evalAsync can now be used:

JS Bin

Scope Phases

Another thing $evalAsync does is to schedule a $digest if one isn't already ongoing. That means, whenever you call $evalAsync you can be sure the function you're deferring will be invoked "very soon" instead of waiting for something else to trigger a digest.

There needs to be some way for $evalAsync to check whether a $digest is already ongoing, because in that case it won't bother scheduling one. For this purpose, Angular scopes implement something called a phase, which is simply a string attribute in the scope that stores information about what's currently going on.

In the Scope constructor, let's introduce a $$phase field, setting it initially as null:

function Scope() {
  this.$$watchers = [];
  this.$$asyncQueue = [];
  this.$$phase = null;
}

Next, let's define a couple of functions for controlling the phase: One for setting it and one for clearing it. Let's also add an additional check to make sure we're not trying to set a phase when one is already active:

Scope.prototype.$beginPhase = function(phase) {
  if (this.$$phase) {
    throw this.$$phase + ' already in progress.';
  }
  this.$$phase = phase;
};

Scope.prototype.$clearPhase = function() {
  this.$$phase = null;
};

In $digest, let's now set the phase as "$digest" for the duration of the outer digest loop:

Scope.prototype.$digest = function() {
  var ttl = 10;
  var dirty;
  this.$beginPhase("$digest");
  do {
    while (this.$$asyncQueue.length) {
      var asyncTask = this.$$asyncQueue.shift();
      this.$eval(asyncTask.expression);
    }
    dirty = this.$$digestOnce();
    if (dirty && !(ttl--)) {
      this.$clearPhase();
      throw "10 digest iterations reached";
    }
  } while (dirty);
  this.$clearPhase();
};

While we're at it, let's tweak $apply so that it also sets the phase for itself. This can be helpful when debugging:

Scope.prototype.$apply = function(expr) {
  try {
    this.$beginPhase("$apply");
    return this.$eval(expr);
  } finally {
    this.$clearPhase();
    this.$digest();
  }
};

And finally we can add the scheduling of the $digest into $evalAsync. It will check the current phase of the scope, and if there isn't one (and no async tasks have been scheduled yet), schedule the digest.

Scope.prototype.$evalAsync = function(expr) {
  var self = this;
  if (!self.$$phase && !self.$$asyncQueue.length) {
    setTimeout(function() {
      if (self.$$asyncQueue.length) {
        self.$digest();
      }
    }, 0);
  }
  self.$$asyncQueue.push({scope: self, expression: expr});
};

With this implementation, when you invoke $evalAsync you can be sure a digest will happen in the near future, regardless of when or where you invoke it.

JS Bin

Running Code After A Digest - $$postDigest

There's one more way you can attach some code to run in relation to the digest cycle, and that's by scheduling a $$postDigest function.

The double dollar sign in the name of the function hints that this is really an internal facility for Angular, rather than something application developers should use. But it's there nevertheless, so we'll also implement it.

Just like $evalAsync, $$postDigest schedules a function to run "later". Specifically, the function will be run after the next digest has finished. Scheduling a $$postDigest function does not cause a digest to be scheduled, so the function execution is delayed until the digest happens for some other reason. As the name implies, $$postDigest functions also run after the digest, so if you make changes to the scope from within $$postDigest, you should call $digest or $apply manually to make sure the changes are picked up.

First, let's add one more queue to the Scope constructor, this one for the $$postDigest functions:

function Scope() {
  this.$$watchers = [];
  this.$$asyncQueue = [];
  this.$$postDigestQueue = [];
  this.$$phase = null;
}

Next, let's add $$postDigest itself. All it does is add the given function to the queue:

Scope.prototype.$$postDigest = function(fn) {
  this.$$postDigestQueue.push(fn);
};

Finally, in $digest, we'll drain the queue and invoke all the functions once the digest has finished:

Scope.prototype.$digest = function() {
  var ttl = 10;
  var dirty;
  this.$beginPhase("$digest");
  do {
    while (this.$$asyncQueue.length) {
      var asyncTask = this.$$asyncQueue.shift();
      this.$eval(asyncTask.expression);
    }
    dirty = this.$$digestOnce();
    if (dirty && !(ttl--)) {
      this.$clearPhase();
      throw "10 digest iterations reached";
    }
  } while (dirty);
  this.$clearPhase();

  while (this.$$postDigestQueue.length) {
    this.$$postDigestQueue.shift()();
  }
};

Here's how one can now schedule a $$postDigest function:

JS Bin

Handling Exceptions

Our current Scope implementation is gradually becoming something that resembles the one in Angular. It is, however, quite brittle. That's because we haven't put much thought into exception handling.

Angular's scopes are quite robust when it comes to errors: When an exception occurs in a watch function, an $evalAsync function, or a $$postDigest function, that does not halt the digest. In our current implementation, an exception in any of those places will cause us to be thrown out of $digest.

We can quite easily fix this, by just wrapping those three invocations inside try...catch.

Angular actually forwards these exceptions to its $exceptionHandler service. Since we don't have such a thing yet, we'll simply log the exceptions to the console for now.

Exception handling for $evalAsync and $$postDigest goes in the $digest function. In both cases, an exception thrown in a scheduled function is logged, and the next one invoked normally:

Scope.prototype.$digest = function() {
  var ttl = 10;
  var dirty;
  this.$beginPhase("$digest");
  do {
    while (this.$$asyncQueue.length) {
      try {
        var asyncTask = this.$$asyncQueue.shift();
        this.$eval(asyncTask.expression);
      } catch (e) {
        (console.error || console.log)(e);
      }
    }
    dirty = this.$$digestOnce();
    if (dirty && !(ttl--)) {
      this.$clearPhase();
      throw "10 digest iterations reached";
    }
  } while (dirty);
  this.$clearPhase();

  while (this.$$postDigestQueue.length) {
    try {
      this.$$postDigestQueue.shift()();
    } catch (e) {
      (console.error || console.log)(e);
    }
  }
};

Exception handling for watches goes to $$digestOnce.

Scope.prototype.$$digestOnce = function() {
  var self  = this;
  var dirty;
  _.forEach(this.$$watchers, function(watch) {
    try {
      var newValue = watch.watchFn(self);
      var oldValue = watch.last;
      if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) {
        watch.listenerFn(newValue, oldValue, self);
        dirty = true;
        watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue);
      }
    } catch (e) {
      (console.error || console.log)(e);
    }
  });
  return dirty;
};

Our digest cycle is now a lot more robust when it comes to exceptions:

JS Bin

Destroying A Watch

When you register a watch, most often you want it to stay active as long as the scope itself does, so you don't really ever explicitly remove it. There are cases, however, when you want to destroy a particular watch while still keeping the scope operational.

The $watch function in Angular actually has a return value. It is a function that, when invoked, destroys the watch that was registered. To implement our own version of this, all we need to do is return a function that removes the watch from the $$watchers array:

Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) {
  var self = this;
  var watcher = {
    watchFn: watchFn,
    listenerFn: listenerFn,
    valueEq: !!valueEq
  };
  self.$$watchers.push(watcher);
  return function() {
    var index = self.$$watchers.indexOf(watcher);
    if (index >= 0) {
      self.$$watchers.splice(index, 1);
    }
  };
};

We can now store the return value of $watch and then invoke it later to remove the watcher:

JS Bin

Looking Ahead

We've already come a long way, and have a perfectly usable implementation of an Angular-style dirty-checking scope system. But there's a lot more to Angular scopes than this.

Perhaps most significantly, scopes in Angular are not always standalone, disconnected objects. Instead, scopes can inherit from other scopes, and watches can watch things not only on the scope they are attached to, but also on that scope's parents. This approach, while conceptually quite simple, is often a source of much confusion to newcomers. That is why scope inheritance will be the subject of the next article in this series.

Later on we will discuss the Angular event system, which is also implemented on Scope.

Know Your AngularJS Inside Out

Build Your Own AngularJS

Build Your Own AngularJS helps you understand everything there is to understand about Angular. By creating your very own implementation of Angular piece by piece, you gain deep insight into what makes this framework tick. Say goodbye to fixing problems by trial and error and hello to reasoning your way through them

Early Access eBook Available Now

comments powered by Disqus