In this series of blog posts I’m going to cover the paper I’m writing at GitHub.
It aims to provide a bit more theoretical overview of some of the AngularJS components in order to show you how the things you are already familiar with (like different Object-Oriented Design Patterns) fit in the picture.
Part one includes only a high level overview of AngularJS, enjoy it.
- two-way data binding
- dependency injection
- separation of concerns
The separation of concerns is achieved by dividing each AngularJS application into separate components, such as:
These components can be grouped inside different modules, which helps to achieve higher level of abstraction and handle complexity. Each of the components encapsulates specific piece of the application’s logic.
The partials are HTML strings. They may contain AngularJS expressions inside the elements or their attributes. One of the distinctions between AngularJS and the others frameworks is the fact that AngularJS’ templates are not in an intermediate format, which needs to be turned into HTML (which is the case with mustache.js and handlebars, for example).
Initially each SPA loads index.html file. In the case of AngularJS this file contains a set of standard and custom HTML attributes, elements and comments, which configure and bootstrap the application. Each sub-sequenced user action requires only load of another partial or change of the state of the application, for example through the data binding provided by the framework.
With AngularJS expressions partials define what kind of actions should be performed for handling different user interactions. In the example above the value of the attribute ng-click states that the method changeFoo of the current scope will be invoked.
For example, if we wire the sample controller above with the partial provided in the previous section the user will be able to interact with the application in few different ways.
Change the value of foo by typing in the input box. This will immediately reflect the value of foo because of the two-way data binding.
Change the value of foo by clicking the button, which will be labeled Click me to change foo!.
All the custom elements, attributes, comments or classes could be recognized as AngularJS directives if they are previously defined as ones.
Another important characteristic of the scopes of any AngularJS application is that they are connected into a prototypical chain (except scopes, which are explicitly stated as isolated). This way any child scope will be able to invoke methods of its parents since they are properties of its direct or indirect prototype.
Scope inheritance is illustrated in the following example:
div#child is associated with ChildCtrl but since the scope injected inside ChildCtrl inherits prototypically from its parent scope (i.e. the one injected inside BaseCtrl), the method foo is accessible by button#parent-method.
In AngularJS the directives are the place where all DOM manipulations should be placed. As a rule of thumb, when you have DOM manipulations in your controller you should create a new direcrive or consider refactoring of already existing one, which could handle the required DOM manipulations.
Each directive has a name and logic associated with it. In the simplest case the directive contains only name and definition of *postLink* function, which encapsulates all the logic required for the directive. In more complex cases the directive could contain a lot of properties such as:
- compile function
- link function
By citing the name of the directives they can be used inside the declarative partials.
In the example above the tag
Since the intent of this paper is not to explain the complete API of AngularJS, we will stop with the directives here.
The filters in AngularJS are responsible for encapsulating logic required for formatting data. Usually filters are used inside the partials but they are also accessible in the controllers, directives, services and other filters through Dependency Injection.
Here is definition of a sample filter, which turns given string to uppercase:
Inside a partial this filter could be used using the Unix’s piping syntax:
Inside a controller the filter could be used as follows:
Every piece of logic, which doesn’t belong to the components described above should be placed inside a service. Usually services encapsulate the domain specific logic, persistence logic, XHR, WebSockets, etc. When the controllers in the application became too “fat” the repetitive code should be placed inside a service.
The service could be injected inside any component, which supports dependency injection (controllers, other services, filters, directives).