English |  Español |  Français |  Italiano |  Português |  Русский |  Shqip

Developing an AngularJS Edge sample chapter

Chapter 2. AngularJS Core Concepts

AngularJS is a framework made of components. Different components are different tools for different problems. If you are familiar with MVC (model-view-controller) frameworks of any type, the distinction between these components will likely make a lot of sense to you. If you are new to MVC, consider it a way to separate an application into presentation, logic and data layers. In addition, MVC provides loose coupling between these layers that allow layers to be swappable. For example, you may have written an entire app in AngularJS, but you want it displayed differently for mobile--just provide a new view; there is no need to write an entirely new application.

The core components are as follows: 

  • Models (this is your data)
  • Controllers (business and some presentation logic)
  • Views (presentation)
  • Services/factories/providers (kind of like libraries)
  • Filters (easy data structure processing in the view)
  • Directives (markup which runs code that modifies the DOM)

What is a Scope?

A fundamental concept to understand in AngularJS is the scope. A scope houses the application model. Not only individual models, but functions as well. Every controller has its own scope. Some directives have their own scope. It's important to know that you can take a scope and visually map it onto a chunk of markup (if you use the AngularJS Batarang, a debugging tool, you can inspect individual HTML nodes and see what is contained within their scope). That's right; it mimics the DOM hierarchy of your view. Have a <p> somewhere with an ngController on it? That <p> now has a scope, and whatever you put in the scope, the <p> can touch. Remove <p> and the scope no longer exists. Some directives create their own scopes, like ngRepeat, and you can look at your markup and know where that new scope lives.

AngularJS is actually closer to an MVVM (Model-View-ViewModel) framework, if you are familiar with that term.  Think of the scope object as the ViewModel. But Igor Minar, one of the AngularJS authors, has declared AngularJS to be a MVW (Model-View-Whatever) framework; it's a just a framework that separates logic from the presentation.  His reasoning is that the lines between these framework types are often blurred by frameworks, and it's not really worth arguing about as long as you're productive in your framework of choice:

What is an AngularJS expression?

It's a JavaScript-ish bit of code that AngularJS understands and processes. Anything that looks like JavaScript within your view is not JavaScript; it's an Angular expression. As such, not all JavaScript is a valid expression! Expressions are evaulated against scopes; if you have an expression foo = 'bar'; the string foo in the current scope will be assigned the value of "bar". Expressions do not deal with undefined and null values as JavaScript does; given something like ng-show="foo.bar"; if foo is undefined, you will not receive an error. The operators === and !== are not necessary; stop worrying about it. You can not use any conditionals, loops, or exceptions within AngularJS expressions. Finally, you can use filters with the | (pipe) operator. AngularJS expression language is in development; look for more features in the future.

Of utmost importance to understand is that there is a root scope from which all scopes inherit, and all scopes inherit prototypically from their parent scope by default. If you set something on the root scope of the application (you can get at this via the $rootScope service), it will be available to all non-isolate scopes (an isolate scope is used in certain directives and does not inherit from its parent, on purpose, in order to not pollute or otherwise overstep its bounds) within the application, no matter where they are. If you need a debug() function (or something; consider the $log service for debugging) available everywhere in the view, put it on the root scope. If you don't need it everywhere, please do not abuse the root scope.

Scopes are simply JavaScript objects, but they have special functions. Here are overviews of some of the more useful ones:

  • $apply: Executes an AngularJS expression from outside of the framework, and typically used in directives. If you have a 3rd library that needs to set a model, you must wrap that assignment in a call to $apply or AngularJS will fail to recognize the change happened, and all sorts of nothing will occur. Watches will not fire, promises will not get resolved, etc., etc.
  • $watch: Watches/observes an AngularJS expression; if that expression changes, the function you pass to $watch will fire. The function will receive "new" and "old" parameters. There is an optional 3rd argument to a $watch; since, by default, $watch evaluates by reference, certain expressions may not trigger a watch function like you think they would. For example, say you had a watch on the expression foo. If foo is reassigned to a new object, the watch will fire. However, if there is an object member bar and it changes, the watch will not fire. The inner object changed, yes, but there was no change in reference. If you want to evaulate by value (a much more expensive operation; use wisely!), supply true as the third parameter to the $watch call. Note that when you define a $watch for the first time, its listener function will be called asynchronously, so it's often necessary to do a comparison between the two parameters as the first line of your listener function if that value is set asynchronously.
  • $eval: Evaluates an expression against the scope and returns its value. Useful in directives, if you want to evaluate a tag's attribute as an AngularJS expression, and use its value later.
  • $on/$broadcast/$emit: You can fire and respond to events up or down the scope inheritance chain using these functions. Use like you would custom jQuery event handlers, for example.

Here are some examples of Scope functions:

// since "click" is actually jQuery doing the work, AngularJS doesn't know about it.
// thus, if we want any watchers to fire when we update our scope object, we must
// use scope.$apply().
someModule.directive('someDirective', function() {
  return function(scope, elm) {
    elm.click(function() {
      scope.$apply(function() {
        scope.foo = 'bar';
      });
    }
  }
});

// this watch will ONLY fire if the object on the scope is reassiged or loses its reference
$scope.$watch('some_object', function(newval, oldval) {
  if (newval !== oldval) {
    $scope.someFunction(newval);
  }
});

// this watch will fire if the object on the scope is reassiged or loses its reference OR
// if an object member changes. more expensive
$scope.$watch('some_object', function(newval, oldval) {
  if (newval !== oldval) {
    $scope.someFunction(newval);
  }
}, true);

// usage: <div some-directive="bar"></div>
someModule.directive('someDirective', function() {
  return function(scope, elm, attrs) {
    // some_object is now the value of bar within the current scope
    var some_object = scope.$eval(attrs.someDirective);
  }
});

// consider this to be in controller Foo; when $broadcast is called all subcontrollers
// will be listening and execute their callback functions. it will also listen on all
// "anotherEvent" events coming from subcontroller Bar, and any other subcontrollers.
$scope.$broadcast('someEvent', obj1);
$scope.$on('anotherEvent', function(evt, data) {
  // do something with obj2
});

// consider this to be in subcontroller Bar
$scope.$on('someEvent', function(evt, data) {
  // do something with the event object and/or "obj1"
});
$scope.$emit('anotherEvent', obj2);

// note that if you want sibling controllers to respond to a $broadcast, you will
// need to do this in a parent controller.  if one is not present, it may make
// sense to use $rootScope:
$rootScope.$broadcast('globalEvent', super_important_stuff);

 Digest already in progress?

If you see this JavaScript error, it means that AngularJS is already attempting to evaluate expressions. The most common solution is to remove your call to $apply because it may not be necessary. Sometimes this doesn't do the trick; however, because if you remove your $apply, your watches don't always fire. Typically I've seen this happen when the same function gets executed from an AngularJS context, synchronously, and outside of an AngularJS context, asynchronously (as with a setTimeout or something). In this case you must ensure your function itself has no $apply in it, but rather each function call is wrapped in an $apply if it is appropriate.


 

A Note About Unit Testing

The following examples in this book will contain unit tests written in Jasmine (http://pivotal.github.com/jasmine/). Jasmine is a BDD (Behavior-Driven Development)-style testing framework written in JavaScript for testing JavaScript. If you are unfamiliar with BDD, it's OK--you don't have to know anything about it to start using Jasmine. The syntax will seem a little strange, but makes sense pretty quickly.

BDD? I was just getting a handle on TDD!

While TDD (test-driven-development) is simply a practice--in a nutshell, write your tests first--BDD is more than this. It is a different way of approaching software development. The main difference between the two is that BDD tests can, in their essence, be written by testers and even end-users. As you are determining your business logic in a specification, you can write down "x should happen when y happens" and convert this directly into a test with a BDD-style testing framework. I haven't read any books about BDD, so I cannot recommend one, but you are encouraged to find out more if you are interested. Remember, you do not need to "subscribe" to BDD to use Jasmine for testing; it works great as a general-purpose unit testing framework.

We'll also assume that the angular-mocks.js file is included when doing these tests, as it provides some convience methods for Jasmine to work with AngularJS.

AngularJS provides functional (or E2E) tests within its online tutorial. While these tests have a lot of value, our application is small and we can get by with manual testing. Once your application grows, it makes more sense to automate functional tests, but I don't find it necessary for our purposes.

The Components

We have models, views, controllers, services/factories/providers, filters, and directives. All of these components will make up a module. A module may be an entire application, a part of an application, or a suite of reusable components. In a two-page application, for example, you may have a module that provides common functionality between the two pages, and a module for each page itself. You would include the "common" module when defining each page's module. If you had other applications, you may want to break out common functionality to yet another module which all applications can use.

  • Models: The most logical place to reference a model will be in a controller, most of the time. If you have disparate controllers that do not share a common parent scope (except for the root scope, of course), you may want to reference your model in a service, because that is a singleton that all components can inject.
  • Views: A view is your markup. Anyone who has worked with a JavaScript templating language, or even a server-side templating language, will feel comfortable in a view. It contains simple logic statements and other bound data (often models), and uses directives and filters. When you reference a model in a view, it's generally in some controller's scope.
  • Controllers: A controller will exist somewhere within the application's scope. It will have its own scope, and any child controllers will prototypically inherit from that scope. It is extremely useful to declare a function in a parent controller which two or more child controllers will use. Controllers are for referencing models and business logic. However, DOM manipulation does not belong in the controller. This needs to be hammered home and well-defined: "DOM manipulation", in my book, would be any code that modifies the existing DOM structure. If you keep your controller free of DOM manipulation, you will have cleaner code that is easily testable. Believe it.
  • Services: A service is an injectable singleton. A collection of functions all relating to a common concept, or a static class-like object would be great as a service. For example, a service could provide functions to call a handful of server-side APIs. You would not need another instance of this service. A service returns nothing.
  • Factories: A factory is injectable, but can return a function, object, or even a primitive (which I've yet to see a use case for, but there it is). If you have a JavaScript "class" you want to let your controllers use, this would be a great place to put it. If you have a utility function but don't want to pollute a namespace, put it in a factory. Maybe you want to return a new instance of some object full of whatever--put it in a factory.
  • Providers: The provider is like a service that generates a factory, if that makes sense. During initialization, you can configure a provider to act a certain way after the application has loaded. The $http service is really a provider, and you can configure its defaults during initialization if you wish. A provider returns nothing, but defines this.$get(); a function to return a factory-like object. When you define provider Foo, you're really definining FooProvider; the thing returned from this.$get is really Foo.

 


 DOM Manipulation outside of a directive

A service, factory, or provider can be used for DOM manipulation, but if it is, it should solely serve that function as to make unit testing straightforward and avoid spaghetti. A great place to put functionality that creates a modal pop-up is in a service. Creating a new "view" could even be construed as business logic. It turns out that using a service in this manner may feel more natural than trying to shoehorn the same functionality into a directive. For example, I have a case in which I provide a showWarning() function (via a factory) to display a warning, the contents of which sometimes fetched over HTTP, to the user, within a modal (note that if we didn't need to show this in a modal, it would be better to simply set the warning text in a model and use ngShow to display it).


 

  • Filters: The filter is great for string or array manipulation to be done in a view. Outside of a view, you can still use a filter, but you're probably better off providing a factory function instead of a filter if you will use it exclusively outside of views.
  • Directives: Finally, the directive is for DOM manpiulation or otherwise controlling the scope or a model from the view. It is never used outside the context of a view. You'll know you need a directive when you find yourself trying to use 3rd-party DOM manipulation libraries. Don't be tempted to use that complicated library in your controller! It will pay off to write a directive to bridge the gap between AngularJS and Library X. Custom validation is best handled in a directive. You can overwrite AngularJS' default directives to change their behavior as well. Don't want the model to update on every keyDown event? Overwrite that input directive! We'll have an example of this later.  Directives essentially extend HTML and can be used to create a domain-specific language for your application or applications.

Conclusion

Those are the core components that make up an AngularJS application.  Think of them as building blocks.  You may need many or few components, depending upon your requirements.
 
In the next chapter we'll explore the model in detail.  It's just data--and you probably have some laying around somewhere.

There has been error in communication with Booktype server. Not sure right now where is the problem.

You should refresh this page.