Demystifying Angular

At their core, AngularJS web apps load in the browser the same way that non-AngularJS apps do; however, they do run slightly differently. The browser loads the AngularJS library as it is building the DOM (as it ordinarily loads any JavaScript libraries).

When the browser fires the DOMContentLoaded event, Angular goes to work. It starts by looking for the ng-app directive (for more information on the ng-app directive, see the Directives explained chapter).

AngularJS also starts initializing when the angular.js script is loaded if the document.readyState is set to complete. This fact is useful if we want to dynamically link the AngularJS script.

If the browser finds the ng-app directive in the DOM, the app is automatically bootstrapped for us. If it doesn’t find the directive, Angular expects us to bootstrap the app manually.

To manually bootstrap an AngularJS app, we can use the Angular method bootstrap(). It makes sense to manually bootstrap an application in some relatively rare cases. For instance, let’s say we want to run an AngularJS app after some other library code runs or we are dynamically creating an element on the fly.

To manually bootstrap an app, we can bootstrap it, like so:

var newElement = document.createElement("div");
angular.bootstrap(newElement, ['myApp']);

If there is no ng-app directive found in the DOM and we have not manually bootstrapped our app, AngularJS does not run. Forgetting to include the ng-app directive on the page can definitely cause some serious issues.

Note that the bootstrap() method will allow us only to bootstrap our angular application one time.

If there is no application specified in the ng-app attribute, Angular loads the app without a specific module. If one is specified, Angular loads the module associated with the directive.

Using ng-app without specifying a module:

<html ng-app>
</html>

Using ng-app with a specified module:

<html ng-app="moduleName">
</html>

Angular uses the value of the ng-app directive to configure the $injector service (we cover this service in depth in the dependency injection chapter).

Once the application is loaded, the $injector creates the $compile service alongside the app’s $rootScope.

Following the $rootScope’s creation, the $compile service takes over. It links the $rootScope with the existing DOM and starts to compile the DOM beginning from where the ng-app directive is set as the root.

How the View Works

When the browser gets its HTML in the normal web flow, it receives the HTML and parses it into a DOM tree. Each element in the DOM tree, called a DOM element, will build a bunch of nodes. The browser is then responsible for laying the DOM tree structure out.

When the browser fetches scripts (from the <script> tag), it pauses the parsing and waits until the script is retrieved (it’s possible to modify this behavior, but this behavior is the default).

When the angular.js script is retrieved, it is executed and it sets up an event listener to listen for the DOMContentLoaded event.

The DOMContentLoaded event is fired when the HTML document has been completed loaded and parsed.

When the event is detected, Angular looks for the ng-app directive and creates the several necessary components it needs to run (i.e., the $injector, the $compile service, and the $rootScope), then it starts parsing the DOM tree.

Compilation Phase

The $compile service traverses the DOM and collects all of the directives that it finds. It then combines all of their linking functions into a single linking function.

This linking function is then used to link the compiled template to the $rootScope (the scope attached to the ng-app DOM element).

It finds the directives in the DOM by looking through the attributes, comments, classes, and the DOM element name.

<span my-directive></span>
<span class="my-directive"></span>
<my-directive></my-directive>
<!-- directive: my-directive -->

For more in-depth directive coverage, check out the directives chapter.

The $compile service walks the DOM tree looking for DOM elements with directives declared on them. When it encounters a DOM element with one or more directives, it orders the directives (based upon their priority), then uses the $injector service to find and collect the directive’s compile function and execute it.

The compile function in directives is the appropriate time to do any DOM transformations or inline templating, as it creates a clone of the template.

// Returns a linking function
var linkFunction = $compile(appElement);
// Calls the linking function, attaching 
// the $rootScope to the domElement
linkFunction($rootScope);

After the compile method runs, per node, the $compile service calls the linking functions. The linking functions set up watches on the directives that are bound to the enclosing scopes. This action creates the live-view.

Finally, after the $compile service is complete, the AngularJS run time is ready to go.

Run time

In a normal browser flow, the event loop waits for events (like the mouse moving, clicking, a keypress, etc). As these events occur, they are placed on the browser’s event queue. If any function handlers are set to react to the event, they are are called with the event as a parameter.

ele.addEventListener('click', function(event) {});

The event loop is augmented a bit with Angular, as Angular provides its own event loop. Directives themselves register event listeners so that when events are fired, the directive function is run in AngularJS $digest loop.

The Angular event loop is called the $digest loop. The $digest loop is composed of two small loops: the evalAsync loop and the $watch list.

When the event is fired, it is called within the context of the directive, which is in the AngularJS context. Functionally, AngularJS calls the directive within an $apply() method on the containing scope. Angular kicks off this entire process when it starts its $digest cycle on the $rootScope, propagating to all of the child scopes.

When Angular falls into the $digest loop, it waits for the $evalAsync queue to empty before it hands the callback execution context back to the browser. The $evalAsync is used to schedule any work that needs to be run outside of the current stack frame, but before the browser renders.

Additionally, the $digest loop waits for the for the $watch expression list, an array of potential expressions that may change during the previous iteration. If a change is detected, then the $watch function is called, and the $watch list is run over again to ensure that nothing has changed.

Note that for any changes detected in the $watch list, AngularJS runs through the list again to ensure that nothing has changed.

Once the $digest loop settles down and no potential changes are detected, the execution leaves the Angular context and passes normally back to the browser, where the DOM will be rendered.

This entire flow happens between every browser event, which is why Angular can be so powerful. It is also possible to inject events from the browser into the AngularJS flow.

 
This page is a preview of ng-book.
Get the rest of this chapter plus 600 pages of the best Angular content on the web.

 

Ready to master AngularJS?

  • What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
  • Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
  • You will learn what you need to know to work professionally with ng-book: The Complete Book on AngularJS or get your money back.
Get it now