Short review
One of the best ways to learn something new is to see how things already familiar to us are used in it. This article does not intend to acquaint readers with the design or design patterns. It offers a basic understanding of OOP concepts, design patterns, and architectural patterns. The purpose of the article is to describe how various software designs and architectural patterns are used in AngularJS and the
SPA written on it.
Introduction
The article begins with an overview of the AngularJS framework. The review explains the main components of AngularJS: directives, filters, controllers, services, scope. The second section lists and describes the various structures and architectural patterns that are implemented inside the framework. Templates are grouped by AngularJS components in which they are used. If some templates are used in multiple components, this will be indicated.
The last section includes several architectural patterns that are commonly used in SPAs built on AngularJS.
AngularJS Short ReviewAngularJS Short Review
AngularJS is a JavaScript web framework developed by Google. He intends to provide a solid foundation for the development of
CRUD SPA. SPA is loaded only once and does not require reloading the page when working with it. This means that all application resources (data, templates, scripts, styles) must be loaded when the main page is loaded or on demand. Since most CRUD applications have common characteristics and requirements, AngularJS intends to provide their optimal set of boxes. Here are some important features of AngularJS:
The division of responsibility is achieved by dividing each AngularJS application into separate components, such as:
- partials
- controllers
- directives
- services
- filters
These components can be grouped within different modules that help achieve a higher level of abstraction. Each component includes a specific part of the application logic.
Partials
Partials is an HTML string. They can contain AngularJS expressions inside elements or their attributes. One of the advantages of AngularJS over other frameworks is that AngularJS templates are not in an intermediate format that needs to be converted to HTML (such as mustache.js)
')
At the beginning, each SPA loads the Index.html file. In the case of AngularJS, this file contains a set of standard (and not so) HTML attributes, elements and comments that customize and launch the application. Each user action requires loading other partials (HTML strings or files with HTML chunks) or changing the state of an application, for example, through data binding provided by the framework.
Example partials:
<html ng-app> <body ng-controller="MyController"> <input ng-model="foo" value="bar"> <button ng-click="changeFoo()">{{buttonText}}</button> <script src="angular.js"></script> </body> </html>
Together with the expressions AngularJS, partials determine what actions should be performed to interact with the user. In the example above, the value of the ng-click attribute means that the changeFoo () method will be called from the current scope.
Controllers
Controllers in AngularJS are normal functions that allow you to handle user and application interaction (for example, mouse events, keyboards, etc.) by adding methods to the scope. All external dependencies for controllers are provided using the DI mechanism in AngularJS. Controllers are also responsible for the interaction of models with partials by adding data to scope. This can be considered as a view model.
function MyController($scope) { $scope.buttonText = 'Click me to change foo!'; $scope.foo = 42; $scope.changeFoo = function () { $scope.foo += 1; alert('Foo changed'); }; }
For example, if we connect the controller presented above to the previous section, the user will be able to interact with the application in several ways:
- Change “foo” by entering data in the input field. This will immediately reflect on the value of “foo” due to two-way data binding.
- Change the value of "foo" by clicking on a button called "Click me to change foo!"
All custom elements, attributes, comments, or classes may be AngularJS directives (if they are predefined).
Scope
The AngularJS scope is a JavaScript object that is available for partials. Scope can include various properties - primitives, objects or methods. All methods added to a scope can be invoked using AngularJS expressions within partials that are associated with this scope or directly invoke the method by any component that has a reference to scope. With the help of the relevant directives, data is added to the scope, and can be associated with the view, so every change in the scope property will be reflected in the view and every change in the view will be displayed in the scope.
Another important feature of scope in any AngularJS application is that they are linked through the prototype inheritance mechanism (with the exception of the isolated scope). Thus, any child scope can use the methods of its parent, since these are properties of its direct or indirect prototype.
The scope inheritance is shown in the following example:
<div ng-controller="BaseCtrl"> <div id="child" ng-controller="ChildCtrl"> <button id="parent-method" ng-click="foo()">Parent method</button> <button ng-click="bar()">Child method</button> </div> </div>
function BaseCtrl($scope) { $scope.foo = function () { alert('Base foo'); }; } function ChildCtrl($scope) { $scope.bar = function () { alert('Child bar'); }; }
The scope ChildCtrl is associated with the # child div, but since the scope ChildCtrl is nested in BaseCtrl, all methods from BaseCtrl are available in ChildCtrl, using prototype inheritance and therefore the foo method will be available when you click the button # parent-method.
Directives
Directives in AngularJS are where all DOM manipulations should be performed. As a rule, if you have a DOM manipulation in the controller, then you need to create a new directive or refactor, which will eliminate the DOM manipulation in the controller. In the simplest case, the directive has the name and the definition of the postLink function, which includes the directive logic. In more complex cases, a directive may contain many properties, such as:
- template
- compile function
- link function
- etc.
Directives can be used in partials, for example:
myModule.directive('alertButton', function () { return { template: '<button ng-transclude></button>', scope: { content: '@' }, replace: true, restrict: 'E', transclude: true, link: function (scope, el) { el.click(function () { alert(scope.content); }); } }; });
<alert-button content="42">Click me</alert-button>
In the example above, the <alert-button> </ alert-button> tag will be replaced by the button element and when you click on the button, a warning appears with the text 42.
Filters
Filters in AngularJS are responsible for encapsulating the logic needed to format the data. Filters are usually used inside partials, but also through DI they are available in controllers, directives, services, or other filters.
Here is a simple example of a filter that converts a string to upper case:
myModule.filter('uppercase', function () { return function (str) { return (str || '').toUpperCase(); }; });
Inside partials, filters can be used using Unix pipelines syntax (Unix's piping):
<div>{{ name | uppercase }}</div>
Inside the controller, the filter can be used as follows:
function MyCtrl(uppercaseFilter) { $scope.name = uppercaseFilter('foo');
Services
Any part of the logic that does not relate to the components described above should be placed in the service. Usually, services encapsulate a specific area of ​​logic, immutable logic, XHR, WebSockets, etc. When the controllers in the application become too thick, the duplicate code should be moved to the services.
myModule.service('Developer', function () { this.name = 'Foo'; this.motherLanguage = 'JavaScript'; this.live = function () { while (true) { this.code(); } }; });
Services can be added to any component that supports DI (controllers, filters, directives, other services):
function MyCtrl(developer) { var developer = new Developer(); developer.live(); }
AngularJS patterns
In the next two sections, we will look at how traditional design and architectural patterns are used in AngularJS components.
In the last chapter, we will look at some architectural patterns that are often used in the development of SPA (and not only) on AngularJS.
Services
Singleton
Singleton is a design pattern that restricts the creation of a class instance to a single object. This is useful when you need to coordinate actions throughout the system. The concept is suitable for systems that function more efficiently when there is only one object or when instances are limited to a certain number of objects.
The UML diagram illustrates the singleton pattern:

When any component needs a dependency, AngularJS resolves it using the following algorithm:
- Takes the name of the dependency and does a search in the hash map, which is defined in the lexical closure. (therefore it has a private scope)
- If a dependency is found AngularJS passes it as a component parameter
- If the dependency is not found:
- AngularJS creates it by calling the factory method or its provider (i.e. $ get). Please note that creating a dependency may require a recursive call using the same algorithm to determine all dependencies of this dependency. This process can lead to cyclical dependency.
- AngularJS caches it inside the hash card mentioned above.
- AngularJS passes it as a parameter to the component for which it is listed.
Better take a look at the AngularJS source code that implements getService:
function getService(serviceName) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); } return cache[serviceName]; } else { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; return cache[serviceName] = factory(serviceName); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { path.shift(); } } }
Imagine that each service is a singleton, because a service is created only once. Cache can be viewed as a singleton manager. Also this implementation is slightly different from the one represented in the UML diagram, because instead of creating a static private link to a singleton inside its constructor, we save the link inside the singleton manager.
Thus, service is actually singleton, but not implemented through the singleton pattern, thereby providing some advantages over the standard implementation:
- improves code testability
- You can control the creation of singleton objects (In this case, the IoC container controls the creation of objects using lazy calls.)
For a more detailed discussion of this topic, you can read Misko Hevery's
article on the Google Testing blog.
Factory Method
Factory Method (Factory Method is also known as Virtual Constructor) is a generic design pattern that provides an interface for subclasses to create instances of a particular class. At the time of creation, successors can determine which class to create. In other words, the Factory delegates the creation of objects to the heirs of the parent class. This allows you to use in the program code not specific classes, but to manipulate abstract objects at a higher level.

Let's look at the following snippet:
myModule.config(function ($provide) { $provide.provider('foo', function () { var baz = 42; return {
Here, the config callback function is used to define the new “provider”. “Provider” is an object with a $ get method. Since there are no interfaces in JavaScript and the language uses “duck” typing, we agreed to call the factory method in the “provider” $ get.
Each service, filter, directive, and controller have a provider (that is, an object that has a $ get factory method), it is responsible for creating component instances.
We can dig a little deeper into the implementation of AngularJS:
In the example, you can see how the $ get method is actually used:
instanceInjector.invoke(provider.$get, provider, undefined, servicename)
In the fragment above, the invoke method is called and the first argument is the factory method ($ get) of the service. Inside the invoke method, the annotate function is called, to which the factory method is also passed as the first argument. The annotate function resolves all dependencies through the DI AngularJS mechanism (discussed above). After resolving all dependencies, the factory method is called:
fn.apply(self, args).
If we reason in terms of the UML diagram described above, then we can call Creator, which through the factory method will call “ConcreteCreator” which will create “Product”.
In this case, we get some advantages using the factory method pattern because it uses indirect instantiation. Thus, the framework affects the layouts / templates for creating new components, because:
- This is the most appropriate moment to create a component.
- allow all component dependencies
- control the number of allowed instances of the component (for service and filter one, but many for the controller)
Decorator
Decorator (Decorator) - a structural design pattern designed to dynamically add additional behavior to an object. The Decorator pattern provides a flexible alternative to creating subclasses to extend functionality.

AngularJS out of the box provides opportunities for expanding and / or enhancing the functionality of existing service 's. Using the decorator or $ provide method you can create a wrapper for any service already defined by you or from a third-party library:
myModule.controller('MainCtrl', function (foo) { foo.bar(); }); myModule.factory('foo', function () { return { bar: function () { console.log('I\'m bar'); }, baz: function () { console.log('I\'m baz'); } }; }); myModule.config(function ($provide) { $provide.decorator('foo', function ($delegate) { var barBackup = $delegate.bar; $delegate.bar = function () { console.log('Decorated'); barBackup.apply($delegate, arguments); }; return $delegate; }); });
The above example defines a new service named “foo”. In the callback function “config”, the method $ provide.decorator is called and the name of the service that we want to decorate is passed to it as the first argument, the function is passed as the second argument, and it actually implements the decorator. $ delegate stores a link to the original service foo. We decorate the service by redefining its bar method. In fact, decorating is just an extension of bar, by turning on another console.log state - console.log ('Decorated') and then calling the original bar method in the appropriate context.
Using the template is especially useful when we need to change the functionality of the service created by third parties. In cases where numerous decorators are needed (for example, when measuring the performance of methods, authorization, registration, etc.), a lot of duplicated code and violation of the
DRY principle can be obtained. In such cases, it is advisable to use
aspect-oriented programming. The AngularJS AOP framework can be found at
github.com/mgechev/angular-aop.Facade
The Facade pattern (facade) is a structural design pattern that allows you to hide the complexity of the system by reducing all possible external calls to a single object, delegating them to the corresponding system objects.
Facade can:
- Make it easier to use libraries, understanding and testing, as the facade has more suitable methods for performing common tasks.
- Make the library more readable for the same reason.
- Reduce the dependence of internal libraries on external code, since most of the code uses facade, this allows the system to be developed more flexibly
- Wrap a poorly designed collection of APIs, one well designed (according to the needs of the task)

AngularJS has several facades. Every time you want to provide a high-level API for some functionality, you practically create a facade.
For example, let's see how we can create an XMLHttpRequest POST request:
var http = new XMLHttpRequest(), url = '/example/new', params = encodeURIComponent(data); http.open("POST", url, true); http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http.setRequestHeader("Content-length", params.length); http.setRequestHeader("Connection", "close"); http.onreadystatechange = function () { if(http.readyState == 4 && http.status == 200) { alert(http.responseText); } } http.send(params);
If we want to send data using the AngularJS $ http service, we can:
$http({ method: 'POST', url: '/example/new', data: data }) .then(function (response) { alert(response); });
or even:
$http.post('/someUrl', data).then(function (response) { alert(response); });
The second option is a pre-configured version that creates an HTTP POST request.
The third option is a high-level abstraction created using the $ resource service and built on top of the $ http service. We will once again consider this service in the Active Record and Proxy sections.
Proxy
Proxy (Deputy) is a structural design pattern that provides an object that controls access to another object, intercepting all calls (performs the function of a container). Proxy can interact with anything: a network connection, a large object in memory, a file or other resources that are expensive or impossible to copy.

We can distinguish three types of proxies:
- Virtual proxy
- Remote proxy
- Protection proxy
In this section, we will look at the Virtual Proxy implemented in AngularJS.
In the snippet below, we call the get method of the $ resource object named User:
var User = $resource('/users/:id'), user = User.get({ id: 42 }); console.log(user);
console.log will display an empty object. Since the AJAX request is executed asynchronously after calling User.get, we will not have user data during the call to console.log. Immediately after calling User.get, a GET request is executed, it returns an empty object and stores a link to it. We can imagine this object as a virtual proxy, it will be filled with data as soon as the client receives a response from the server.
How does it work in AngularJS? Let's look at the following snippet:
function MainCtrl($scope, $resource) { var User = $resource('/users/:id'), $scope.user = User.get({ id: 42 }); }
<span ng-bind="user.name"></span>
After executing the code snippet shown above, the user property of the $ scope object will be an empty object ({}), which means that user.name will be undefined and will not be displayed. After the server returns a response for a GET request, AngularJS fills this object with data received from the server. During the next $ digest cycle, AngularJS will detect changes in $ scope.user and this will update the view.
Active record
Active Record is an object that stores data and behavior. Usually, most of the data in this object is permanent, the responsibility of the Active Record object is to take care of communicating with the database to create, update, search, and delete data. It can delegate this responsibility to lower level objects, but a call to an Active Record object or its static methods will lead to interaction with the database.

AngularJS defines service $ resource. In the current version of AngularJS (1.2+), it is distributed as a separate module and is not included in the kernel.
According to the $ resource documentation:
$ resource is a factory for creating $ resource objects that allow you to interact with RESTful data sources on the server side. The $ resource object has methods that provide high-level behavior, without the need to interact with the $ http low-level service.
This shows how $ resource can be used:
var User = $resource('/users/:id'), user = new User({ name: 'foo', age : 42 }); user.$save();
Calling $ resource creates a constructor for instances of our model. Each of the model instances will have methods that can be used for different CRUD operations.
Thus, we can use the constructor function and its static methods:
User.get({ userid: userid });
The code above will immediately return an empty object and save a link to it. After the answer is received and analyzed, AngularJS will fill the object with the received data (see proxy).
You can find more detailed documentation on the "magic" of $ resource and AngularJS.
So Martin Fowler states that:
An Active Record object must take care of communication with the database in order to create ...
$ resource does not implement the full Active Record template, as it interacts with a RESTful service instead of a database. Anyway, we can consider it as “Active Record for interacting with RESTful”.
Intercepting Filters
Creates a chain of filters to perform simple pre / post request processing tasks.

In some cases, you will need to do some kind of pre / post HTTP request processing. In this case, Intercepting Filters are designed for the pre / post processing of HTTP requests / responses, for logging, security, or any other task that needs the body or header of the request. Typically, the Intercepting Filters pattern includes a chain of filters, each of which processes data in a specific order. The output of each filter is the input to the next.
In AngularJS we have an idea about Intercepting Filters in $ httpProvider. $ httpProvider has an interceptors property (interceptors), it includes a list of objects. Each object has properties: request, response, requestError, responseError.
The requestError is called if an error occurred in the previous interceptor or was rejected; accordingly, the responseError is called when the previous response interceptor raised an exception.
Below is a basic example of how you can add interceptors using an object literal: $httpProvider.interceptors.push(function($q, dependency1, dependency2) { return { 'request': function(config) {
Directives
Composite
The composite pattern (linker) is a structural design pattern. The composite template describes how to group objects so that they can be accessed, as well as to a single object. The purpose of composite is to compose objects in a tree structure, to represent the hierarchy from the particular to the whole.
According to the Gang of Four, MVC is nothing more than a combination:- Strategy
- Composite
- Observer
They claim that the presentation is a composition of components. AngularJS has a similar situation. Representations are formed by a composition of directives and DOM elements on which these directives are built.Let's look at the following example: <!doctype html> <html> <head> </head> <body> <zippy title="Zippy"> Zippy! </zippy> </body> </html>
myModule.directive('zippy', function () { return { restrict: 'E', template: '<div><div class="header"></div><div class="content" ng-transclude></div></div>', link: function (scope, el) { el.find('.header').click(function () { el.find('.content').toggle(); }); } } });
This example creates a directive that is a component of the user interface. The created component (with the name “zippy”) has a title and content. Clicking on its title switches the visibility of its contents.From the first example, we can see that the DOM tree is a composition of elements. The root component is html, immediately followed by the nested elements head, body, and so on ...In the second example, we see that the property of the template directive contains markup with the directive ng-transclude. This means that there can be another ng-transclude directive inside the “zippy” directive, that is, a composition of directives. Theoretically, we can do infinite nesting of components until we reach the end node.Interpreter
Interpreter (interpreter) is a behavioral design pattern that specifies how to define expressions in a language. The basic idea is to classify each character (terminal or non-terminal) in a specialized programming language. The syntax tree (an example of a composition template) of expressions is used to analyze (interpret) an expression.
Using $ parse, AngularJS provides its own DSL interpreter implementation (Domain Specific Language). Using DSL simplifies and modifies javascript. The main difference between AngularJS and JavaScript expressions is that AngularJS expressions:- may include filters with UNIX similar syntax
- exclude no exceptions
- have no state flow control (exceptions, cycles, conditions can also use a ternary operator)
- run in the resulting context (current $ scope context)
Inside $ parse, two main components are defined:
When an expression is received, it is broken into lexemes and cached (due to performance issues).Terminal expressions in AngularJS DSL are defined as follows: var OPERATORS = { 'null':function(){return null;}, 'true':function(){return true;}, 'false':function(){return false;}, undefined:noop, '+':function(self, locals, a,b){
Each function associated with each terminal symbol can be represented as an implementation of the Abstract Abstract interface (abstract expressions).The client interprets the resulting expression in a specific context - a certain $ scope.A few simple AngularJS expressions:
Template View
Converts data to HTML by embedding markers in an HTML page.
Dynamically displaying a page is not such an easy task. This is due to the large number of string concatenation, manipulations and problems. The easiest way to build a dynamic page is to write your markup and include several expressions in it that will be processed in a specific context, as a result the entire template will be converted to the final format. In our case, this format will be HTML (or even DOM). This is exactly what template engines do — they get the DSL, process it in the appropriate context, and then convert it to the final format.Templates are very often used on the back-end. For example, you can embed PHP code inside HTML and create dynamic pages, or you can use Smarty or eRuby in Ruby to embed Ruby code into static pages.There are many templating engines in JavaScript, such as mustache.js, handlebars, etc. Most of them work with a string-like pattern. The template can be stored in various ways:- static file to be received by AJAX
- built-in script inside your view
- line inside your javascript code
For example:
<script type="template/mustache"> <h2>Names</h2> {{#names}} <strong>{{name}}</strong> {{/names}} </script>
The template engine turns a string into DOM elements by combining it with the resulting context. Thus, all embedded expressions are parsed and replaced with their values.For example, if we process the template shown above in the context of the object: {names: ['foo', 'bar', 'baz']} then we get: <h2>Names</h2> <strong>foo</strong> <strong>bar</strong> <strong>baz</strong>
In fact, templates in AngularJS are plain HTML. They are not in an intermediate format, like most templates. What does the AngularJS compiler do to bypass the DOM tree and find already known directives (elements, attributes, classes or even comments)? When AngularJS finds any of these directives it calls the logic associated with it, it may include defining various expressions in the context of the current $ scope.For example:
<ul ng-repeat="name in names"> <li>{{name}}</li> </ul>
In the context of scope: $scope.names = ['foo', 'bar', 'baz'];
You will get the same result as above. The main difference is that the template is not located inside the script tag, here it is just HTML.Scope
Observer
The observer pattern (observer) is a behavioral design pattern, in which an object is called a subject, stores a list of its dependencies, called observers, and notifies them of any state changes, usually by calling one of their methods. Mainly used to implement distributed event processing systems.
In AngularJS applications, there are two main ways to interact between scope.First, call the methods of the parent scope from the child scope. This is possible because the child scope inherits the prototype of its parent, as mentioned above (see Scope). This allows one-way interaction from the child to the parent. Sometimes it is necessary to call the child scope method or notify it about the occurrence of an event in the parent scope. AngularJS provides a built-in viewer template that allows you to do this.The second possible use of the observer pattern is when several scope are subscribed to events, but the scope in which it occurs does not know anything about it. This reduces the scope of connectivity, they should not know anything about the other scope.Each scope in AngularJS has public $ on, $ emit and $ broadcast methods. The $ on method takes the event name and callback function as arguments. This function can be represented as an observer - an object that implements the observer interface (In JavaScript, all functions are first class, therefore we can provide only the implementation of the notification method). function ExampleCtrl($scope) { $scope.$on('event-name', function handler() {
Thus, the current scope subscribes to the event-name event. When it occurs in any of the parent or child scope, the handler will be called.The $ emit and $ broadcast methods are used to trigger events, respectively, up and down the inheritance chain. For example:
function ExampleCtrl($scope) { $scope.$emit('event-name', { foo: 'bar' }); }
In the example above, scope generates an event-name event up for all scope. This means that every parent scope that is subscribed to the event name will be notified and its handler will be called.The same thing happens when the $ broadcast method is called. The only difference is that the event will be passed down - for all child scope. Each scope can subscribe to any event with multiple callback functions (i.e., it can associate several observers with this event).In the JavaScript community, this template is better known as publish / subscribe.Chain of Responsibilities
Chain of Responsibilities is a behavioral design pattern that consists of a command object and a sequence of processing objects (handlers). Each handler contains logic that determines the type of command it can process. Then the command goes to the next handler in the chain. The template also contains a mechanism for adding new handlers to the end of this chain.
As shown above, scope forms a hierarchy known as the scope chain. Some of these scope are isolated, which means that they do not inherit the prototypes of their parent scope, but are associated with it through its $ parent property.After calling $ emit or $ broadcast, an event occurs that starts moving (up or down depending on the method called) along the scope chain, it can be represented as an event bus or, more precisely, as a chain of duties. Each subsequent scope may:- Handle the event and pass it to the next scope in the chain
- Process the event and stop its distribution.
- Skip the event to the next scope in the chain, without processing it
- Stop event distribution without processing it
In the example below, ChildCtrl generates an event that propagates up the scope chain. Here, each parent scope (ParentCtrl and MainCtrl) must process the event by writing to the console: “foo received”. If any of the scope should be the final recipient, it can call the stopPropagation method in the event object. myModule.controller('MainCtrl', function ($scope) { $scope.$on('foo', function () { console.log('foo received'); }); }); myModule.controller('ParentCtrl', function ($scope) { $scope.$on('foo', function (e) { console.log('foo received'); }); }); myModule.controller('ChildCtrl', function ($scope) { $scope.$emit('foo'); });
The handlers in the UML diagram shown above are different scope added to the controller.Command
Command is a behavioral design pattern in which an object is used to encapsulate all the necessary information and call a method over time. This information includes the name of the method, the object to which the method belongs, and the values ​​of the method parameters.
In AngularJS, the Command pattern allows you to describe the implementation of data binding.When we want to link our model with a view, we can use the ng-bind directive (for one-way data binding) and the ng-model (for two-way data binding). For example, if we want each model change to be displayed in the view: <span ng-bind="foo"></span>
Now every time we change the value of foo, the text inside the span tag will also be changed. You can also use more complex expressions: <span ng-bind="foo + ' ' + bar | uppercase"></span>
In the example above, the value of the span tag is the sum of the foo and bar values ​​in uppercase. What happens inside?In each scope there is a $ watch method. When the AngularJS compiler finds the ng-bind directive, it creates a new observer for the expression foo + '' + bar | uppercase, ($ scope. $ watch ("foo + '' + bar | uppercase", function () {/ * body * /});). The callback function will be called every time the value of the expression changes. In this case, the callback function will update the value of the span tag.Here are the first few lines of the $ watch implementation: $watch: function(watchExp, listener, objectEquality) { var scope = this, get = compileToFn(watchExp, 'watch'), array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, get: get, exp: watchExp, eq: !!objectEquality };
We can see watcher as Command. The Command expression will be evaluated in each $ digest cycle. As soon as AngularJS detects a change in expression, it will call the listener function. Watcher includes all the necessary information for monitoring and delegating the execution of the command to the listener (the actual recipient). We can represent the $ scope as the Client and the $ digest loop as Invoker commands.Controllers
Page controller
An object that processes requests for a specific page or action on a site. Martin Fowler.
According to 4 controllers per page:The page controller template allows data input from the received page, performing the requested actions for the model and determines the correct presentation for the resulting page. The separation of control logic from the rest of the view code.Due to the large amount of duplicate code on the pages (for example, footers, headers, code that takes care of the user's session), controllers can form a hierarchy. AngularJS has controllers that are limited in scope. They do not accept user requests, since it is the responsibility of $ route or $ state, and the ng-view / ui-view directives are responsible for the display.Just like in page controllers, AngularJS controllers are responsible for user interaction, providing updated models. These models after adding to the scope are not protected from viewing, all methods included in the view eventually become user actions (scope methods). Another similarity between page controllers and AngularJS controllers is the hierarchy that they form. This corresponds to the scope hierarchy of visibility. Thus, by simple actions we can isolate the base controllers.AngularJS controllers are very similar to ASP.NET WebForms, since their responsibilities are almost the same. Here is an example of a hierarchy between several controllers: <!doctype html> <html> <head> </head> <body ng-controller="MainCtrl"> <div ng-controller="ChildCtrl"> <span>{{user.name}}</span> <button ng-click="click()">Click</button> </div> </body> </html>
function MainCtrl($scope, $location, User) { if (!User.isAuthenticated()) { $location.path('/unauthenticated'); } } function ChildCtrl($scope, User) { $scope.click = function () { alert('You clicked me!'); }; $scope.user = User.get(0); }
This example illustrates the easiest way to reuse logic with the base controller, but in any case, it is not recommended to put the authorization logic in controllers in “production” applications. Access to different routes can be defined at a higher level of abstraction.ChildCtr is responsible for displaying the model in a view and handling actions, such as clicking a button called “Click”.Others
Module Pattern
In fact, this is not a design pattern from the "gang of four" and not even one of the "Patterns of Enterprise Application Architecture". This is a traditional JavaScript design pattern, the purpose of which is to provide encapsulation.Using the template module, you can get data privacy based on functional and lexical scope. Each module can have zero and more private methods that are hidden in the local scope.This function returns an object that provides a public API for this module: var Page = (function () { var title; function setTitle(t) { document.title = t; title = t; } function getTitle() { return title; } return { setTitle: setTitle, getTitle: getTitle }; }());
In the example above, IIFE (Immediately-Invoked Function Expression is an expression of an immediately called function) that returns an object after the call, with two methods (setTitle and getTitle). The returned object is assigned to the Page variable. In this case, the user of the Page object does not have direct access to the title variable, which is defined within the local IIFE scope.Template module is very useful when defining service in AngularJS. Using this template, we can achieve (and actually achieve) privacy: app.factory('foo', function () { function privateMember() {
After we add foo to any other component, we will not be able to use private methods, only public ones. This solution is quite powerful, especially when we create libraries for reuse.Data mapper
The Data Mapper template is a data access layer that performs bidirectional data transfer between a persistent data store (often a relational database) and in-memory data. The purpose of this template is to store in memory the representation of persistent data, independently from each other and from oneself.
As already mentioned, the Data Mapper is used for bidirectional data transfer between the persistent data store and the in-memory data. Typically, an AngularJS application interacts with a server API that is written in any server language (Ruby, PHP, Java, JavaScript, etc.).If we have a RESTful API, service $ resource will help us interact with the server in Active Record in a similar style. Although some applications do not return data from the server in the most appropriate format that we want to use on the front-end.For example, let's assume we have an application in which each user has:- name
- address
- list of friends
And our API which has the following methods:- GET / user /: id - returns the name and address of the received user
- GET / friends /: id - returns the list of friends of this user.
One solution is to use two different services, one for the first method and the other for the second. Perhaps a more appropriate solution would be to use one service, which is called User, it loads the user's friends when we request User: app.factory('User', function ($q) { function User(name, address, friends) { this.name = name; this.address = address; this.friends = friends; } User.get = function (params) { var user = $http.get('/user/' + params.id), friends = $http.get('/friends/' + params.id); $q.all([user, friends]).then(function (user, friends) { return new User(user.name, user.address, friends); }); }; return User; });
Thus, we created a pseudo Data Mapper that adapts to our API in accordance with the requirements of the SPA.We can use User as follows: function MainCtrl($scope, User) { User.get({ id: 1 }).then(function (data) { $scope.user = data; }); }
And the corresponding pattern: <div> <div> Name: {{user.name}} </div> <div> Address: {{user.address}} </div> <div> Friends with ids: <ul> <li ng-repeat="friend in user.friends">{{friend}}</li> </ul> </div> </div>
Links
- Wikipedia . The design patterns are wikipedia.
- AngularJS' documentation
- AngularJS' git repository
- Page Controller
- Patterns of Enterprise Application Architecture (P of EAA)
- Using Dependancy Injection to Avoid Singletons
- Why would one use the Publish/Subscribe pattern (in JS/jQuery)?