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:
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.
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