Web apps are increasingly getting more and more complicated these days. Libraries and frameworks of the past just aren't built to grow with these evolving needs. Using jQuery to manage a complex web app just isn't going to cut it. Using Backbone will take you only so far before it falls flat on its face and you find yourself in a tangled mess. Fortunately, new libraries are emerging to meet these challenges head on, improve development over time, and allow us to continue to push forward with building better apps. AngularJS is one of those.
Before we dive in to bi-directional data binding specifics let's take a brief look at what AngularJS offers.
Working with AngularJS' Built-In Tools
AngularJS is complex enough that you can get lost figuring out what's important. Additionally, AngularJS is un-opinionated, i.e., it doesn't dictate what your objects look like or how to communicate with a server. While I personally like the way AngularJS handles these things, it makes the learning curve slightly steeper. To help counteract this sharp ascent, I've taken the liberty of compiling my thoughts gained from a year of learning and using AngularJS.
Use AngularJS factories to create your object instances. In your factory definition return your
prototypes (or in CoffeeScript, your
Classes). You can use your prototype (or Class) within the factory to wrap a resource returned from an API. Here you can insulate yourself from any oddities with the API's data model.
If the application is your hand and API's json is chocolate, then a factory is a hard candy wrapper that keeps things clean. We borrowed concepts from our favorite languages and created a base accessor factory to wrap all json coming in from the API. When we needed to expand we simply subclassed the base factory into specific factories for the object, adding functions for logical checks. Because we put this logic in the object itself, we could do things like asking a user object if it is allowed to do this or that i.e.
user.may_use_feature() or return computed attributes
Use services when calling an API or storing application state. Services are singletons. Updates to them are propagated to each place they are referenced. When using a service to wrap an API, it's a great place to contain any differing assumptions about the API that do not fit with your application.
We used services to wrap all API calls allowing us to have a comfortable boundary between our application and API. By structuring the application this way, we had a place for all of the pre and post processing needed. It also allows organizing API abstractions. As one example, we had a rich internal representation of a user. Fully hydrating our
User required pulling information from multiple sources. We had our user service make multiple API calls to retrieve all the information, wrap it in our User factory and presented it to the rest of the application as one resource.
We had lots of success with this Service/Factory pattern while working with an ever-evolving API. As the API changed, we only had to refactor the small corresponding parts in the respective Service or Factory. See AngularJS Services and Factories done right for an example how to put it all together.
$scope. Many controllers with limited responsibilities works better in the long run than fewer with larger roles. AngularJS allows multiple active controllers, and they can be arranged in a hierarchy.
Each controller instance gets it's own
this in context of a controller. If you find yourself struggling with controller communication, you should delegate that particular part to a service. A child controller can read it's parents
$scope, but anything else becomes problematic.
For readability have an HTML view "own" a controller instead of being assigned one in the router. This allows better controller and view reuse between AngularJS applications, and speeds up development when trying to determine which controller is governing view behavior. It also encourages smaller controllers.
Directives are for extending HTML elements and attributes then associating behavior with it.
After bi-directional binding, directives seem to be the second most advertised feature of AngularJS. They are however; one of the dark corners of AngularJS as acknowledged by it's creators
When starting out, forgo writing directives. You need a deep knowledge of AngularJS' internals to debug directives when they misbehave. Instead, start by writing a small controller and view. You can refactor these into a directive later when it feels right.
Take care with other libraries that modify the DOM, including jQuery. Anything DOM related within
ng-app is owned by AngularJS, and it expects it won't be altered outside of a
digest cycle. Mainly as a challenge for ourselves, we avoided using jQuery entirely in a large project full of complex interactions. We were very satisfied with the result. If you choose to include jQuery in your project be sure to read up on AngularJS'
Structured, yet flexible
AngularJS provides enough scaffolding to build on top of, but not to too much as to obstruct developer creativity. Encouraging organization and modularization; Factories and Services are simply AngularJS DI constructs to assist the developer in organizing their code. What roles either of them play are entirely up to the developer.
The ultimate reason for using AngularJS is not for what it can do, but what it frees the developer from having to do. Free from wiring up complex bindings, developers can focus on creating a product instead of creating a system to create a product (related: essential complexity vs. accidental complexity) Picking the right tool for a job is part of software craftsmanship and the benefits of AngularJS coincide nicely when the goal is a web application.
Also read the followup blog post AngularJS Services and Factories done right