From 22549144e5875952f2b1ebff70b8de294e64547b Mon Sep 17 00:00:00 2001 From: Wassim CHEGHAM Date: Sun, 19 Jul 2015 04:49:51 +0200 Subject: [PATCH 01/29] use custom gulp-markdown-pdf module --- .gitignore | 4 ++- gulpfile.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 14 ++++++++- 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 gulpfile.js diff --git a/.gitignore b/.gitignore index 0a0af45..af26d66 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -build/README.pdf \ No newline at end of file +build/ +node_modules/ +temp/ diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..16989cb --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,84 @@ +var gulp = require('gulp'); +var markdownpdf = require('gulp-markdown-pdf'); +var path = require('path'); +var replace = require('gulp-replace'); +var package = require('./package.json'); +var rename = require('gulp-rename'); +var rimraf = require('gulp-rimraf'); +var runSequence = require('run-sequence'); +var glob = require('glob'); + +var TITLE = 'AngularJS in Patterns'; + +function genericTask(lang){ + + gulp.task('generate:pdf:' + lang, function() { + + var files = ['./temp/*.md']; + if (lang === 'eng'){ + files = './temp/README.md'; + } + else if(lang !== 'all'){ + files = ['./temp/*-'+lang+'.md']; + } + + return gulp.src(files) + .pipe(markdownpdf({ + cwd: path.resolve('./temp/'), + layout: 'github' + })) + .on('error', function(err){ + gutil.log(gutil.colors.red('doc task failed'), err); + }) + .pipe(rename(function (path) { + var lang = 'ENG'; + if(path.basename.indexOf('-') >= 0){ + lang = path.basename.replace('README-', '').toUpperCase(); + } + path.basename = TITLE + ' ('+lang+')'; + path.extname = '.pdf' + })) + .pipe(gulp.dest('./build/')); + }); + +} + +// build custom tasks for i18n + +glob.sync('./temp/README-*.md').map(function(file){ + + return file.replace('README-', ''); + +}).concat(['all', 'eng']).forEach(function(lang){ + + genericTask(lang); + gulp.task('doc:pdf:'+lang, function(cb){ + runSequence('clean', ['copy:images', 'copy:md'], 'generate:pdf:'+lang, cb); + }); + +}); + +gulp.task('default', function(cb){ + runSequence('clean', ['copy:images', 'copy:md'], 'doc:pdf:all', cb); +}); + +gulp.task('copy:md', function(){ + return gulp.src(['README.md', 'i18n/README-*.md']) + + // @todo I have no idea where should the TOC go?! + // for now, let's keep the TOC content and remove these markers + .pipe(replace('', '')) + .pipe(replace('', '')) + + // preapre the image paths for the renderer + .pipe(replace(/https:\/\/rawgit.com\/mgechev\/angularjs-in-patterns\/master\/images/g, '.')) + .pipe(gulp.dest('./temp/')); +}); + +gulp.task('copy:images', function(){ + return gulp.src(['images/*.svg','meta.json']).pipe(gulp.dest('./temp')); +}); + +gulp.task('clean', function() { + return gulp.src('./temp/', { read: false }).pipe(rimraf()); +}); diff --git a/package.json b/package.json index 4cf963a..400ea87 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,22 @@ "url": "git://github.com/mgechev/angularjs-patterns.git" }, "author": "mgechev", + "contributors": [{ + "name" : "Wassim Chegham", + "email" : "maneki.nekko@gmail.com", + "url" : "https://github.com/manekinekko/" + }], "license": "MIT", "gitHead": "2009434a6dfdbe5f06f81253fb853840af753b36", "readmeFilename": "README.md", "devDependencies": { - "markdown-styles": "https://github.com/mgechev/markdown-styles/tarball/master" + "glob": "^5.0.14", + "gulp": "^3.9.0", + "gulp-markdown-pdf": "https://github.com/manekinekko/gulp-markdown-pdf.git#master", + "gulp-rename": "^1.2.2", + "gulp-replace": "^0.5.3", + "gulp-rimraf": "^0.1.1", + "markdown-styles": "https://github.com/mgechev/markdown-styles/tarball/master", + "run-sequence": "^1.1.1" } } From d7d830a4c3263cf7cca6e8ad8eab6b247ada9a0f Mon Sep 17 00:00:00 2001 From: mgechev Date: Sun, 19 Jul 2015 11:58:44 +0300 Subject: [PATCH 02/29] Remove build.sh --- build.sh | 9 --------- gulpfile.js | 10 +++++----- 2 files changed, 5 insertions(+), 14 deletions(-) delete mode 100755 build.sh diff --git a/build.sh b/build.sh deleted file mode 100755 index aab1b7e..0000000 --- a/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -mkdir temp && cp -r ./meta.json ./README.md ./images/* temp -perl -i -0777 -pe 's/().*()/'"$"'$2/s' ./temp/README.md -sed -i.bak 's|https://rawgit.com/mgechev/angularjs-in-patterns/master/images|.|g' ./temp/README.md -rm -rf ./temp/*.bak -mv ./temp/README.md ./temp/index.md -./node_modules/.bin/generate-md --layout minko-book --input ./temp --output ../angularjs-in-patterns-gh-pages -rm -rf temp \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 16989cb..3a57be7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,7 +16,7 @@ function genericTask(lang){ var files = ['./temp/*.md']; if (lang === 'eng'){ - files = './temp/README.md'; + files = './temp/README.md'; } else if(lang !== 'all'){ files = ['./temp/*-'+lang+'.md']; @@ -46,11 +46,11 @@ function genericTask(lang){ // build custom tasks for i18n glob.sync('./temp/README-*.md').map(function(file){ - + return file.replace('README-', ''); }).concat(['all', 'eng']).forEach(function(lang){ - + genericTask(lang); gulp.task('doc:pdf:'+lang, function(cb){ runSequence('clean', ['copy:images', 'copy:md'], 'generate:pdf:'+lang, cb); @@ -64,13 +64,13 @@ gulp.task('default', function(cb){ gulp.task('copy:md', function(){ return gulp.src(['README.md', 'i18n/README-*.md']) - + // @todo I have no idea where should the TOC go?! // for now, let's keep the TOC content and remove these markers .pipe(replace('', '')) .pipe(replace('', '')) - // preapre the image paths for the renderer + // preapre the image paths for the renderer .pipe(replace(/https:\/\/rawgit.com\/mgechev\/angularjs-in-patterns\/master\/images/g, '.')) .pipe(gulp.dest('./temp/')); }); From 4c60c5b551fe6b2a8a2d008eff153376c7711cdf Mon Sep 17 00:00:00 2001 From: Wassim CHEGHAM Date: Mon, 20 Jul 2015 01:41:09 +0200 Subject: [PATCH 03/29] french translation (wip 55%) --- i18n/README-fr-fr.md | 678 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 i18n/README-fr-fr.md diff --git a/i18n/README-fr-fr.md b/i18n/README-fr-fr.md new file mode 100644 index 0000000..07a590d --- /dev/null +++ b/i18n/README-fr-fr.md @@ -0,0 +1,678 @@ +# Les patrons de conception avec AngularJS 1.x + + + +## Table des matières + +* [Traductions](#translations) +* [Abstract](#abstract) +* [Introduction](#introduction) +* [Vue d'ensemble d'AngularJS](#angularjs-overview) + * [Les vues partielles](#partials) + * [Les contrôleurs](#controllers) + * [Le scope](#scope) + * [Les directives](#directives) + * [Les filtres](#filters) + * [Les services](#services) +* [Les patrons de conception d'AngularJS](#angularjs-patterns) + * [Les services](#services-1) + * [Singleton](#singleton) + * [Factory Method](#factory-method) + * [Decorator](#decorator) + * [Facade](#facade) + * [Proxy](#proxy) + * [Active Record](#active-record) + * [Intercepting Filters](#intercepting-filters) + * [Les directives](#directives-1) + * [Composite](#composite) + * [Interpreter](#interpreter) + * [Template View](#template-view) + * [Le scope](#scope-1) + * [Observer](#observer) + * [Chaîne de responsabilités](#chain-of-responsibilities) + * [Command](#command) + * [Les contrôlleurs](#controller-1) + * [Page Controller](#page-controller) + * [Les autres patrons](#others) + * [Module Pattern](#module-pattern) + * [Data Mapper](#data-mapper) + * [Le service en tant que Observer](#observer-pattern-as-an-external-service) +* [Références](#references) + + + +## Traductions + +- [Version original](https://github.com/mgechev/angularjs-in-patterns/blob/master/README.md) +- [Traduction japonaise](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) par [morizotter](https://twitter.com/morizotter) +- [Traduction russe](http://habrahabr.ru/post/250149/) + +## Abstract + +Parfois, le meilleur moyen d'apprendre une nouvelle technologie est d'essayer de retrouver des concepts que l'on connait déjà, et voir comment ils sont implémentés dans cette technologie. +Ce document n'a pas pour vocation d'expliquer en détails les principes d'architecture logiciels ou de la POO. +Le but de ce papier est de décrire comment les différents patrons de conception logiciel et aussi ceux d’architecture peuvent être implémentés par AngularJS ou n'importe quelle Single Page Application codée avec AngularJS. + +## Introduction + +Ce document commence par une vue d'ensemble du framework AngularJS. Dans cette vue d'ensemble, nous décrivons les différents composants du framework - les directives, filtres, contrôleurs, services et scope. La second section liste et décrit les différents patrons de conception qui sont implémentés par le framework. Ces patrons sont groupés par les composants AngularJS dans lesquels ils sont implémentés. Lorsqu'un patron est utilisé dans plusieurs composants, cela sera mentionné explicitement. + +La dernière section contient quelques patrons d'architecture souvent rencontrés dans la plupart des applications AngularJS. + +## Vue d'ensemble d'AngularJS + +AngularJS est un framework JavaScript développé par Google. Il propose de solides bases pour le développement d'application mono-page (Single-Page Application - SPA). +Une SPA est une application qui une fois chargée, ne requiert pas un rechargement total de la page lorsque l'utilisateur interagit avec elle. Cela signifie que toutes les ressources de l'application (données, templates, scripts et styles) devrait être chargées lors de la première requête, voire mieux, à la demande. +Le constat étant que les applications SPA de type CRUD ont des caractéristiques communes, AngularJS fournit de base tout un ensemble de mécanismes et fonctionnalités tels que : + +- le binding bi-directionnel +- l'injection de dépendances +- la séparation des préoccupations +- les tests +- une abstraction + +La séparation des préoccupations est rendue possible en divisant une application AngularJS en plusieurs composants, tels que : + +- les vues partielles +- les contrôleurs +- les directives +- les services +- les filtres + +Ces composants peuvent être groupés dans des modules, qui offre une abstraction supplémentaire pour mieux gérer la complexité des SPA. Chaque composant encapsule une partie bien spécifique et bien définit de la logique de l'application. + +### Les vues partielles + +Les vues partielles sont de l'HTML. Elles peuvent contenir des expressions AngularJS au sein des éléments HTML ou des attributs. L'une des différences majeures entre AngularJS et les autres frameworks réside dans le fait que les templates AngularJS ne sont pas un format intermédiaire qui devrait être transformé en HTML (ce qui est le cas de mustache.js ou handlebars, par exemple). + +Initialement, chaque SPA charge le fichier `index.html`. Dans le cas d'AngularJS, ce fichier contient du code HTML standard enrichit avec des éléments, attributs et commentaire dont le but est de configurer et de démarrer l'application. Chaque interaction de l'utilisateur avec l'application requiert simplement le chargement d'une vue partielle ou le chargement d'un état de l'application, à travers le biding de données fournit par AngularJS. + +**Exemple de vue partielle** + +```HTML + + + + + + + + + +``` + +Avec les expressions, les vues partielles définissent quelles actions doivent traiter les actions de l'utilisateur. Dans l'exemple précédent, la valeur de l'attribut `ng-click` précise que la méthode `changeFoo` du *scope* courant sera invoquée. + +### Les contrôleurs + +Les contrôleurs d'AngularJS sont des fonctions JavaScript qui gèrent les interactions de l'utilisateur avec l'application web (par exemple, les événements de la souris, des touches claviers...etc), en attachant des méthodes au *scope*. Toues les dépendances externes d'un contrôleur sont fournit via le mécanisme d'ID ou injection de dépendances d'AngularJS. Les contrôleurs ont également pour but de fournir le *modèle* à la vue en attachant des données dans le *scope*. Nous pouvons voir ces données comme des *modèle vue* (*view model*) + +``` +JavaScript +function MyController($scope) { + $scope.buttonText = 'Cliquer moi pour changer foo!'; + $scope.foo = 42; + + $scope.changeFoo = function () { + $scope.foo += 1; + alert('Foo a été changé'); + }; +} +``` +Par exemple, si nous associons le contrôleur de l'exemple ci-dessus avec la vue de l'exemple dans la section précédente, l'utilisateur va pouvoir interagir avec l'application de différentes manières : + +1. Changer la valeur de `foo` en saisissant une valeur dans le champ de saisie. Ceci va immédiatement refléter la valeur de `foo` grâce au mécanisme de biding bi-directionnel. +2. Changer la valeur de `foo` en cliquant sur le bouton, qui aura le libellé `Cliquer moi pour changer foo!`. + +Tous les éléments, attributs, commentaires ou classes CSS personnalisés permettant d'enrichir l'HTML sont appelés des *directives* AngularJS. + +### Le scope + +Dans AngularJS, le scope est un objet JavaScript qui est exposé au vues partielles. Le scope peut contenir plusieurs propriétés - primitives, objets ou méthodes. Toutes les méthodes attachées au scope peuvent être invoquées en évaluant l'expression AngularJS à l'intérieur de la vue associée au scope en question, ou simplement via un appel direct à la méthode par un composant donné. En utilisant les *directives* adéquates, les données attachées au scope peuvent être attachées (data-bound) de telle sorte que chaque changement dans la vue met à jour - immédiatement et automatiquement - une propriété du scope ; ainsi que chaque changement d'une propriété du scope est immédiatement reflété dans la vue. + +Une autre caractéristique très importante des scopes réside dans le fait que les scopes sont hiérarchisés en suivant le modèle de la chaîne des prototypes de JavaScript (excepté les scopes *isolés*). De cette manière, chaque scope fils a la possibilité d'invoquer des méthodes de ses parents, puisque ces derniers sont des propriétés directes ou indirectes de son prototype. + +L'héritage des scopes est illustré dans l'exemple suivant : + +```HTML +
+
+ + +
+
+``` + +```JavaScript +function BaseCtrl($scope) { + $scope.foo = function () { + alert('Base foo'); + }; +} + +function ChildCtrl($scope) { + $scope.bar = function () { + alert('Child bar'); + }; +} +``` + +Le contrôleur `ChildCtrl` est associé à l'élément `div#child`, mais puisque le scope injecté par `ChildCtrl` hérite le prototype du scope de son parent (celui injecté par le contrôleur `BaseCtrl`). Ceci fait que la méthode `foo` est accessible par `button#parent-method`. + +### Les directives + +Dans AngularJS, les directives sont l'endroit où toutes les manipulations du DOM doivent être implémentées. Lorsque vous devez manipuler le DOM, vous devez créer une directive ou réutiliser celles qui existent. +Chaque directive possède un nom et une logique associée. Dans le cas le plus simple, une directive contient seulement un nom et une définition de la fonction *postLink*, qui encapsule toute la logique requise pour la directive. Pour les cas les plus complexes, une directive peut contenir d'autres propriétés tels que : + +- un template +- une fonction `compile` +- une fonction `link` +- etc... + +Pour utiliser une directive dans une vue, il suffit de référencer son nom. Par exemple : + +```JavaScript +myModule.directive('alertButton', function () { + return { + template: '', + scope: { + content: '@' + }, + replace: true, + restrict: 'E', + transclude: true, + link: function (scope, el) { + el.click(function () { + alert(scope.content); + }); + } + }; +}); +``` + +```HTML +Cliquer moi +``` + +Dans cet exemple, la balise `` sera remplacé par la balise `button`. Lorsque l'utilisateur cliquera sur ce bouton, le message `42` sera affiché. + +Nous n'irons pas plus loin dans les explications des directives. Ce n'est pas le but de ce papier. + +### Les filtres + +Dans AngularJS, les filtres sont responsables d'encapsuler toute la logique nécessaire pour formater des données. Souvent, les filtres sont utilisés au sein des vues, mais il est également possible de les appeler dans des contrôleurs, directives, services ainsi que d'autres filtres grâce à l'injection de dépendances. + +Voici une définition d'un filtre dont le rôle est de transformer une chaîne de caractères en majuscule : + +```JavaScript +myModule.filter('uppercase', function () { + return function (str) { + return (str || '').toUpperCase(); + }; +}); +``` + +Ce filtre peut être utilisé au sein d'une vue en utilisant le symbole `|` d'Unix : + +```HTML +
{{ name | uppercase }}
+``` + +Au sein d'un contrôleur, le filtre peut être utilisé de cette façon : + + +```JavaScript +function MyCtrl(uppercaseFilter) { + $scope.name = uppercaseFilter('foo'); //FOO +} + +### Les services + +Dans AngularJS, les services sont responsables d'accueillir la logique métier des composants, la logique de persistance, les appels XHR, WebSockets, etc. Lorsque le contrôleur devient trop `gros`, le code superflu et répétitif devrait être déplacé dans un service. + +```JavaScript +myModule.service('Developer', function () { + this.name = 'Foo'; + this.motherLanguage = 'JavaScript'; + this.live = function () { + while (true) { + this.code(); + } + }; +}); +``` + +Le service peut être injecté dans n'importe quel composant, supportant l'injection de dépendances (les contrôleurs, d'autres services, les filtres, les directives). + +```JavaScript +function MyCtrl(Developer) { + var developer = new Developer(); + developer.live(); +} +``` + +## AngularJS Patterns + +Dans les sections suivantes, nous allons voir comment les patrons de conception traditionnels sont utilisés dans les composants d'AngularJS. + +Ensuite, dans le dernier chapitre, nous verrons quels sont les différents patrons d'architecture fréquemment rencontrés dans les Single-Page Application développés avec AngularJS, mais pas seulement. + +### Les services + +#### Le patron Singleton + +>Le patron Singleton vise à assurer qu'il n'y a toujours qu'une seule instance d'un objet en fournissant une interface pour la manipuler. C'est un des patrons les plus simples. L'objet qui ne doit exister qu'en une seule instance comporte une méthode pour obtenir cette unique instance et un mécanisme pour empêcher la création d'autres instances. + +Ce patron est illustré dans le diagramme UML ci-dessous : + +![Singleton](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/singleton.svg "Fig. 1") + +Lorsqu'une dépendance doit être injectée par AngularJS dans un composant, voici l'algorithme utilisé par le framework : + +- Prendre le nom de la dépendance et le rechercher dans une `hash map`, qui est définit au sein de sa portée lexical (ainsi elle reste privée). +- Si la dépendance existe, AngularJS la passe en tant que paramètre au composant qui l'a demandé. +- Si la dépendance n'existe pas : + - AngularJS créé une nouvelle instance de cette dépendance en invoquant la `factory method` de son provider: la méthode `$get`. A noter qu'au moment de l'instanciation, cette dépendance peut éventuellement déclencher un appel récursive de cet algorithme, afin de résoudre toutes les dépendances requises par cette dépendance. Ce qui peut conduire à un souci de dépendances circulaires. + - AngularJS met en cache cette instance, dans la hash map mentionnée précédemment. + - AngularJS transmet cette instance en tant que paramètre au composant qui a demandé cette dépendance. + +Voici un aperçu du code source d'AngularJS, de la méthode `getService` : + +```JavaScript +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(); + } + } +} +``` + +Nous pouvons dire que chaque service est un singleton car chaque service est instancié qu'une seule fois. Nous pouvons également considérer le cache comme un manageur de singletons. Il existe une légère variation du diagramme UML illustré ci-dessus parce qu'au lieu de garder une référence statique, au sein de son constructeur, nous conservons cette référence au sein du manager de singleton (illustré dans le bout de code ci-dessus en tant que `cache`). + +De cette manière, les services sont réellement des singletons mais ne sont pas implémentés à travers le patron Singleton, ce qui offre quelques avantages par rapport à l'implémentation classique : + +- améliore la testabilité de votre code source +- vous pouvez contrôler la création des objets singletons (dans notre cas, le conteneur IoC (Inversion de Contrôle) le contrôle pour nous, en instanciant le singleton). + +Si vous voulez en savoir plus, vous pouvez lire l'[article](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) de Misko Hevery. + +#### Factory Method + +>Le patron Factory Method fournit une interface pour créer un objet qui laisse la possibilité aux sous-classes de décider quel type d'objet créer. Ce patron est utilisé lorsque la classe d'un objet n'est pas connue au moment de la compilation. Une méthode pour créer un objet factory method est définie dans une classe abstraite, et implémentée dans les différentes sous-classes. La factory method peut également comporter une implémentation par défaut. + +![Factory Method](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/factory-method.svg "Fig. 2") + +Considérons le code suivant : + +```JavaScript +myModule.config(function ($provide) { + $provide.provider('foo', function () { + var baz = 42; + return { + //Factory method + $get: function (bar) { + var baz = bar.baz(); + return { + baz: baz + }; + } + }; + }); +}); + +``` + +Dans le code ci-dessus, nous utilisons la callback `config` dans le but de définir un nouveau "provider" ou un "fournisseur". Un "provider" est un objet qui possède une méthode `$get`. Puisque JavaScrit n'offre pas d'interface, la convention veut que l'on nomme cette méthode de telle sorte. + +Chaque service, filtre ou contrôleur possède un "provider" qui est responsable de créer une instance de ce composant. + +Jetons un oeil sur l'implémentation d'AngularJS : + +```JavaScript +//... + +createInternalInjector(instanceCache, function(servicename) { + var provider = providerInjector.get(servicename + providerSuffix); + return instanceInjector.invoke(provider.$get, provider, undefined, servicename); +}, strictDi)); + +//... + +function invoke(fn, self, locals, serviceName){ + if (typeof locals === 'string') { + serviceName = locals; + locals = null; + } + + var args = [], + $inject = annotate(fn, strictDi, serviceName), + length, i, + key; + + for(i = 0, length = $inject.length; i < length; i++) { + key = $inject[i]; + if (typeof key !== 'string') { + throw $injectorMinErr('itkn', + 'Incorrect injection token! Expected service name as string, got {0}', key); + } + args.push( + locals && locals.hasOwnProperty(key) + ? locals[key] + : getService(key) + ); + } + if (!fn.$inject) { + // this means that we must be an array. + fn = fn[length]; + } + + return fn.apply(self, args); +} +``` + +D'après ce code, nous remarquons que la méthode `$get` est utilisé à ce niveau : + +```JavaScript +instanceInjector.invoke(provider.$get, provider, undefined, servicename) +``` + +Dans ce bout de code, la méthode `invoke` de l'objet `instanceInjector` est invoquée avec la "factory method" `$get` en premier paramètre. Dans le corps de la fonction `invoke` la fonction `annotate` est appelée avec en premier paramètre la "factory method". Cette fonction `annotate` permet de résoudre toutes les dépendances à travers le mécanisme d'injection de dépendance d'AngularJS, vu précédemment. Lorsque toutes les dépendances sont résolues, la "factory metho" est invoquée : `fn.apply(self, args)`. + +Si nous faisons le lien avec le diagramme UML précédent (en figure 2), nous pouvons associer le "provider" à "ConcreteCreator" et le composant créé, un "Product". + +Utiliser le patron Factory Method offre des avantages dans notre cas grâce à l'indirection qu'il introduit. Cela permet au framework de d'avoir le contrôle de la création des nouveaux composants, comme par exemple : + +- Le moment approprié d'instancier un composant. +- La résolution de toutes les dépendances requises par un composant. +- Le nombre d'instance autorisé par composant : une seule pour les filtres et les services, et plusieurs instances pour les contrôleurs. + +#### Decorator + +>Ce patron permet d'attacher dynamiquement des responsabilités à un objet. Une alternative à l'héritage. Ce patron est inspiré des poupées russes. Un objet peut être caché à l'intérieur d'un autre objet décorateur qui lui rajoutera des fonctionnalités, l'ensemble peut être décoré avec un autre objet qui lui ajoute des fonctionnalités et ainsi de suite. Cette technique nécessite que l'objet décoré et ses décorateurs implémentent la même interface, qui est typiquement définie par une classe abstraite. + +![Decorator](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/decorator.svg "Fig. 4") + +AngularJS fournit de base un moyen d'étendre et d'enrichir les fonctionnalités des services existants. En utilisant la méthode `decorator` de l'objet `$provider`, il est possible de créer des "wrapper" de n'importe quel service définit ou fournit par un module tiers : + + +```JavaScript +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; + }); +}); +``` + +L'exemple ci-dessus définit un nouveau service appelé `foo`. Dans la phase de configuration, la méthode `$provider.decorator` est invoquée avec l'argument `"foo"`, qui est le nom du service que nous souhaitons décorer, ainsi qu'un second paramètre qui est l'implémentation de la décoration souhaitée. `$delegate` garde une référence du service original `foo`. En utilisant l'injection de dépendances d'AngularJS, nous récupérant cette référence dans le décorateur. Nous décorons ensuite le service en surchargeant sa méthode `bar`, en invoquant une instruction supplémentaire : `console.log('Decorated');`. Puis nous donnons la main au service original. + +Ce patron est utile si nous souhaitons modifier le comportement des services tiers. Dans certains cas où plusieurs décorations "similaires" sont nécessaires (mesure de performance, gestion des droits, gestion des logs, etc.), nous risquons d'introduire beaucoup de duplication de code et violer ainsi le principe DRY. Dans ces cas précis, il est recommandé de se recourir aux principe de la [Programmation Orienté Aspects (AOP)](http://en.wikipedia.org/wiki/Aspect-oriented_programming). Il existe un framework AOP pour AngularJS, vous pouvez le trouver à [cette adresse](https://github.com/mgechev/angular-aop). + +#### Facade + +>Ce patron fournit une interface unifiée sur un ensemble d'interfaces d'un système. Il est utilisé pour réaliser des interfaces de programmation. Si un sous-système comporte plusieurs composants qui doivent être utilisés dans un ordre précis, une classe façade sera mise à disposition, et permettra de contrôler l'ordre des opérations et de cacher les détails techniques des sous-systèmes. Une façade peut : + +>1. rendre une librairie plus simple à utiliser et à tester. + +>2. rendre une librairie plus simple à comprendre. + +>3. offrir une meilleure flexibilité dans le développement de la librairie. + +>4. englober une collection d'API mal conçues en une seule API bien conçue (en foncton des tâches). + +![Facade](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/facade.svg "Fig. 11") + +Il existe peut de façades dans AngularJS. A chaque fois que vous voulez fournir une API de haut niveau pour une certaine fonctionnalité, vous finissez par créer une façade. + +Par exemple, regardons comment créer une requête POST avec `XMLHttpRequest` : + + ```JavaScript +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); +``` + +Voici la même chose en utilisant le service `$http` d'AngularJS : + +```JavaScript +$http({ + method: 'POST', + url: '/example/new', + data: data +}) +.then(function (response) { + alert(response); +}); +``` +ou encore : + +```JavaScript +$http.post('/someUrl', data) +.then(function (response) { + alert(response); +}); +``` + +La seconde option offre une version pre-configurée qui créé une requête POST vers une URL donnée. + +Le service `$resource` d'AngularJS est un service construit autour de `$http` et apporte une abstraction supplémentaire. Nous verrons ce nouveau dans les sections [Enregistrement Actif (Active Record)](#active-record) et [Proxy](#proxy). + +#### Proxy + +>Ce patron est un substitut d'un objet, qui permet de contrôler l'utilisation de ce dernier. Un proxy est un objet destiné à protéger un autre objet. Le proxy a la même interface que l'objet à protéger. Il peut être créé par exemple pour permettre d’accéder à distance à un objet (via un middleware). Un proxy, dans sa forme la plus simple, ne protège rien du tout et transmet tous les appels de méthode à l'objet cible. + +![Proxy](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/proxy.svg "Fig. 9") + +Nous pouvons différencier trois types de proxy : + +- Proxy virtuel +- Proxy distant +- Proxy de protection + +Dans cette section, nous allons parler de l'implémentation du proxy virtuel dans AngularJS. + +Dans le code suivant, il y a un appel vers la méthode `get` de l'instance `$resource` référencée par `User` : + +```JavaScript +var User = $resource('/users/:id'), + user = User.get({ id: 42 }); +console.log(user); // {} +``` + +L'appel de `console.log` affiche un objet vide. La requête AJAX, qui est émise lors de l'appel de la méthode `User.get`, est une requête asynchrone, nous n'avons pas vraiment l'objet `user` lorsque `console.log` est invoquée. Juste après que `User.get` déclenche la requête GET, elle retourne un objet vide et garde une référence vers ce dernier. Cet objet représente donc un proxy virtuel, qui sera mis à jour avec les données récupérées du serveur lors de la réception de la réponse. + +Comment cela fonctionne dans AngularJS? Considérons le code suivant : + + +```JavaScript +function MainCtrl($scope, $resource) { + var User = $resource('/users/:id'), + $scope.user = User.get({ id: 42 }); +} +``` + +```html + +``` + +>**Pro tip** : Il n'est pas conseillé d'utiliser `$resource` directement dans un contrôleur. Préférez le mettre dans une factory ou un service ! + +Lorsque ce code s'exécute, la propriété `user` de l'objet `$scope` est initialement vide (`{}`), ce qui signifie que `user.name` sera `undefined` et rien ne sera rendu dans la vue. En interne, AngularJS garde une référence de cet objet vide. Lorsque le serveur répond à la requête GET, AngularJS met à jour l'objet avec les données reçues. Lors de l'itération suivante de la boucle du `$digest`, AngularJS détecte des changements dans l'objet `$scope.user`, ce qui déclenche le rafraichissement de la vue. + +#### Enregistrement Actif (Active Record) + +>Active Record est une approche pour lire les données d'une base de données. Les attributs d'une table ou d'une vue sont encapsulés dans une classe. Ainsi l'objet, instance de la classe, est lié à un tuple de la base. L'objet Active Record encapsule donc les données ainsi que le comportement. + +![Active Record](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/active-record.svg "Fig. 7") + +AngularJS définit un service nommé `$resource` distribué dans un module additionnel. D'après la [documentation](https://docs.angularjs.org/api/ngResource/service/$resource) d'AngularJS du service `$resource` : + +>Une factory pour la création d'objet permettant une interaction avec les données des sources RESTful. +>L'objet retourné possède des méthodes d'action offrant une abstraction très haut niveau et ne nécessitant pas une interaction avec le service `$http`. + +Voici comment le service `$resource` peut être utilisé : + +```JavaScript +var User = $resource('/users/:id'), + user = new User({ + name: 'foo', + age : 42 + }); + +user.$save(); +``` + +l'appel à `$resource` retourne un constructeur permettant d'instancier des objets de notre modèle `User`. Chaque instance possède des méthodes correspondantes à des opérations de CRUD. + +Le constructeur possède également des méthodes statiques équivalentes aux méthodes d'instances : + +```JavaScript +var user = User.get({ userid: userid }); +``` + +Ce code retourne un [proxy virtuel](#proxy). + +Les puristes dirons de suite que le service `$resource` n'implémente pas le parton Active Record, puisque ce dernier stipule que la responsabilité d'un tel patron de conception est de prendre en charge la communication avec la base de données. Or `$resource` communique lui avec des Web Services RESTful. A vrai dire, tout dépend de quel point de vue nous nous situons, pour une application SPA, une resource RESTful est considérée comme une source de données. Voilà ! problème résolue. + +Vous pouvez trouver plus de détails concernant les pouvoirs du service `$resource` [ici](http://blog.mgechev.com/2014/02/05/angularjs-resource-active-record-http/). + +#### Intercepting Filters + +>Créé une chaîne de filtres composables afin d'implémenter des tâches de pré-processing et post-processing récurrentes lors de l'émission des requêtes. + +![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/intercepting-filters.svg "Fig. 3") + +Dans certains cas, il peut arriver que vous deviez traiter les requêtes HTTP sortantes et entrantes afin par exemple d'ajouter une gestion de logs, ajouter un mécanisme de sécurité, ou tout autre tâche concernée par le corps de la requête ou de ses entêtes. Les filtres d'interception inclus une chaîne de filtres pouvant chacun traiter des données dans un ordre définit. La sortie de chaque filtre est l'entrée du filtre suivant. + +Dans AngularJS, nous rencontrons ce patron dans le service `$httpProvider`. Ce service possède un tableau de propriétés appelé `interceptors` qui contient une liste d'objet. Chaque objet peut posséder une ou toutes les propriété suivantes : `request`, `response`, `requestError`, `responseError`. + +L'objet `requestError` est un intercepteur qui est appelé lorsque l'intercepteur `request` de la requête précédente jette une erreur ou un exception ou bien lorsqu'un promise a été rejetée. De même, `responseError` est appelé lorsque l'intercepteur `response` de la réponse précédente rencontre une erreur. + +Voici un exemple basique : + +```JavaScript +$httpProvider.interceptors.push(function($q, dependency1, dependency2) { + return { + 'request': function(config) { + // same as above + }, + 'response': function(response) { + // same as above + } + }; +}); +``` + +### Les directives + +#### Composite + +#### Interpreter + +#### Template View + +### Scope + +#### Observer + +#### Chain of Responsibilities + +#### Command + +### Controllers + +#### Page Controller + +### Others + +#### Module Pattern + +### Data Mapper + +### Observer Pattern as an External Service + +##### About + +##### Controller Example + +## References + +1. [Wikipedia](https://en.wikipedia.org/wiki). The source of all brief descriptions of the design patterns is wikipedia. +2. [AngularJS' documentation](https://docs.angularjs.org) +3. [AngularJS' git repository](https://github.com/angular/angular.js) +4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) +5. [Patterns of Enterprise Application Architecture (P of EAA)](http://martinfowler.com/books/eaa.html) +6. [Using Dependancy Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) +7. [Why would one use the Publish/Subscribe pattern (in JS/jQuery)?](https://stackoverflow.com/questions/13512949/why-would-one-use-the-publish-subscribe-pattern-in-js-jquery) + + + + + + + + + + + + + + + + + + + + + + + + +(WIP...) From 2b49a8df28d00aa706e0f8d9ba92665b0c07a35f Mon Sep 17 00:00:00 2001 From: Wassim CHEGHAM Date: Tue, 21 Jul 2015 00:46:47 +0200 Subject: [PATCH 04/29] french translation (wip 80%) --- i18n/README-fr-fr.md | 388 +++++++++++++++++++++++++++++++++---------- 1 file changed, 300 insertions(+), 88 deletions(-) diff --git a/i18n/README-fr-fr.md b/i18n/README-fr-fr.md index 07a590d..63922fb 100644 --- a/i18n/README-fr-fr.md +++ b/i18n/README-fr-fr.md @@ -4,40 +4,40 @@ ## Table des matières -* [Traductions](#translations) -* [Abstract](#abstract) -* [Introduction](#introduction) -* [Vue d'ensemble d'AngularJS](#angularjs-overview) - * [Les vues partielles](#partials) - * [Les contrôleurs](#controllers) - * [Le scope](#scope) - * [Les directives](#directives) - * [Les filtres](#filters) - * [Les services](#services) -* [Les patrons de conception d'AngularJS](#angularjs-patterns) - * [Les services](#services-1) - * [Singleton](#singleton) - * [Factory Method](#factory-method) - * [Decorator](#decorator) - * [Facade](#facade) - * [Proxy](#proxy) - * [Active Record](#active-record) - * [Intercepting Filters](#intercepting-filters) - * [Les directives](#directives-1) - * [Composite](#composite) - * [Interpreter](#interpreter) - * [Template View](#template-view) - * [Le scope](#scope-1) - * [Observer](#observer) - * [Chaîne de responsabilités](#chain-of-responsibilities) - * [Command](#command) - * [Les contrôlleurs](#controller-1) - * [Page Controller](#page-controller) - * [Les autres patrons](#others) - * [Module Pattern](#module-pattern) - * [Data Mapper](#data-mapper) - * [Le service en tant que Observer](#observer-pattern-as-an-external-service) -* [Références](#references) +- [Traductions](#translations) +- [Abstract](#abstract) +- [Introduction](#introduction) +- [Vue d'ensemble d'AngularJS](#angularjs-overview) + - [Les vues partielles](#partials) + - [Les contrôleurs](#controllers) + - [Le scope](#scope) + - [Les directives](#directives) + - [Les filtres](#filters) + - [Les services](#services) +- [Les patrons de conception d'AngularJS](#angularjs-patterns) + - [Les services](#services-1) + - [Singleton](#singleton) + - [Factory Method](#factory-method) + - [Decorator](#decorator) + - [Facade](#facade) + - [Proxy](#proxy) + - [Active Record](#active-record) + - [Intercepting Filters](#intercepting-filters) + - [Les directives](#directives-1) + - [Composite](#composite) + - [Interpreter](#interpreter) + - [Template View](#template-view) + - [Le scope](#scope-1) + - [Observer](#observer) + - [Chaîne de responsabilités](#chain-of-responsibilities) + - [Command](#command) + - [Les contrôlleurs](#controller-1) + - [Page Controller](#page-controller) + - [Les autres patrons](#others) + - [Module Pattern](#module-pattern) + - [Data Mapper](#data-mapper) + - [Le service en tant que Observer](#observer-pattern-as-an-external-service) +- [Références](#references) @@ -50,7 +50,9 @@ ## Abstract Parfois, le meilleur moyen d'apprendre une nouvelle technologie est d'essayer de retrouver des concepts que l'on connait déjà, et voir comment ils sont implémentés dans cette technologie. + Ce document n'a pas pour vocation d'expliquer en détails les principes d'architecture logiciels ou de la POO. + Le but de ce papier est de décrire comment les différents patrons de conception logiciel et aussi ceux d’architecture peuvent être implémentés par AngularJS ou n'importe quelle Single Page Application codée avec AngularJS. ## Introduction @@ -62,7 +64,9 @@ La dernière section contient quelques patrons d'architecture souvent rencontré ## Vue d'ensemble d'AngularJS AngularJS est un framework JavaScript développé par Google. Il propose de solides bases pour le développement d'application mono-page (Single-Page Application - SPA). + Une SPA est une application qui une fois chargée, ne requiert pas un rechargement total de la page lorsque l'utilisateur interagit avec elle. Cela signifie que toutes les ressources de l'application (données, templates, scripts et styles) devrait être chargées lors de la première requête, voire mieux, à la demande. + Le constat étant que les applications SPA de type CRUD ont des caractéristiques communes, AngularJS fournit de base tout un ensemble de mécanismes et fonctionnalités tels que : - le binding bi-directionnel @@ -89,7 +93,7 @@ Initialement, chaque SPA charge le fichier `index.html`. Dans le cas d'AngularJS **Exemple de vue partielle** -```HTML +``` HTML @@ -108,7 +112,7 @@ Avec les expressions, les vues partielles définissent quelles actions doivent t Les contrôleurs d'AngularJS sont des fonctions JavaScript qui gèrent les interactions de l'utilisateur avec l'application web (par exemple, les événements de la souris, des touches claviers...etc), en attachant des méthodes au *scope*. Toues les dépendances externes d'un contrôleur sont fournit via le mécanisme d'ID ou injection de dépendances d'AngularJS. Les contrôleurs ont également pour but de fournir le *modèle* à la vue en attachant des données dans le *scope*. Nous pouvons voir ces données comme des *modèle vue* (*view model*) -``` +``` JavaScript function MyController($scope) { $scope.buttonText = 'Cliquer moi pour changer foo!'; @@ -120,6 +124,7 @@ function MyController($scope) { }; } ``` + Par exemple, si nous associons le contrôleur de l'exemple ci-dessus avec la vue de l'exemple dans la section précédente, l'utilisateur va pouvoir interagir avec l'application de différentes manières : 1. Changer la valeur de `foo` en saisissant une valeur dans le champ de saisie. Ceci va immédiatement refléter la valeur de `foo` grâce au mécanisme de biding bi-directionnel. @@ -135,7 +140,7 @@ Une autre caractéristique très importante des scopes réside dans le fait que L'héritage des scopes est illustré dans l'exemple suivant : -```HTML +``` HTML
@@ -144,7 +149,7 @@ L'héritage des scopes est illustré dans l'exemple suivant :
``` -```JavaScript +``` function BaseCtrl($scope) { $scope.foo = function () { alert('Base foo'); @@ -163,6 +168,7 @@ Le contrôleur `ChildCtrl` est associé à l'élément `div#child`, mais puisque ### Les directives Dans AngularJS, les directives sont l'endroit où toutes les manipulations du DOM doivent être implémentées. Lorsque vous devez manipuler le DOM, vous devez créer une directive ou réutiliser celles qui existent. + Chaque directive possède un nom et une logique associée. Dans le cas le plus simple, une directive contient seulement un nom et une définition de la fonction *postLink*, qui encapsule toute la logique requise pour la directive. Pour les cas les plus complexes, une directive peut contenir d'autres propriétés tels que : - un template @@ -172,7 +178,7 @@ Chaque directive possède un nom et une logique associée. Dans le cas le plus s Pour utiliser une directive dans une vue, il suffit de référencer son nom. Par exemple : -```JavaScript +``` JavaScript myModule.directive('alertButton', function () { return { template: '', @@ -191,7 +197,7 @@ myModule.directive('alertButton', function () { }); ``` -```HTML +``` HTML Cliquer moi ``` @@ -205,7 +211,7 @@ Dans AngularJS, les filtres sont responsables d'encapsuler toute la logique néc Voici une définition d'un filtre dont le rôle est de transformer une chaîne de caractères en majuscule : -```JavaScript +``` JavaScript myModule.filter('uppercase', function () { return function (str) { return (str || '').toUpperCase(); @@ -213,25 +219,25 @@ myModule.filter('uppercase', function () { }); ``` -Ce filtre peut être utilisé au sein d'une vue en utilisant le symbole `|` d'Unix : +Ce filtre peut être utilisé au sein d'une vue en utilisant le symbole `|` d'UNIX : -```HTML +``` HTML
{{ name | uppercase }}
``` Au sein d'un contrôleur, le filtre peut être utilisé de cette façon : - -```JavaScript +``` javascript function MyCtrl(uppercaseFilter) { $scope.name = uppercaseFilter('foo'); //FOO } +``` ### Les services Dans AngularJS, les services sont responsables d'accueillir la logique métier des composants, la logique de persistance, les appels XHR, WebSockets, etc. Lorsque le contrôleur devient trop `gros`, le code superflu et répétitif devrait être déplacé dans un service. -```JavaScript +``` javascript myModule.service('Developer', function () { this.name = 'Foo'; this.motherLanguage = 'JavaScript'; @@ -245,7 +251,7 @@ myModule.service('Developer', function () { Le service peut être injecté dans n'importe quel composant, supportant l'injection de dépendances (les contrôleurs, d'autres services, les filtres, les directives). -```JavaScript +``` JavaScript function MyCtrl(Developer) { var developer = new Developer(); developer.live(); @@ -262,7 +268,7 @@ Ensuite, dans le dernier chapitre, nous verrons quels sont les différents patro #### Le patron Singleton ->Le patron Singleton vise à assurer qu'il n'y a toujours qu'une seule instance d'un objet en fournissant une interface pour la manipuler. C'est un des patrons les plus simples. L'objet qui ne doit exister qu'en une seule instance comporte une méthode pour obtenir cette unique instance et un mécanisme pour empêcher la création d'autres instances. +> Le patron Singleton vise à assurer qu'il n'y a toujours qu'une seule instance d'un objet en fournissant une interface pour la manipuler. C'est un des patrons les plus simples. L'objet qui ne doit exister qu'en une seule instance comporte une méthode pour obtenir cette unique instance et un mécanisme pour empêcher la création d'autres instances. Ce patron est illustré dans le diagramme UML ci-dessous : @@ -279,7 +285,7 @@ Lorsqu'une dépendance doit être injectée par AngularJS dans un composant, voi Voici un aperçu du code source d'AngularJS, de la méthode `getService` : -```JavaScript +``` JavaScript function getService(serviceName) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { @@ -310,17 +316,17 @@ De cette manière, les services sont réellement des singletons mais ne sont pas - améliore la testabilité de votre code source - vous pouvez contrôler la création des objets singletons (dans notre cas, le conteneur IoC (Inversion de Contrôle) le contrôle pour nous, en instanciant le singleton). -Si vous voulez en savoir plus, vous pouvez lire l'[article](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) de Misko Hevery. +Si vous voulez en savoir plus, je vous invite à lire l'[article](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) de Misko Hevery. #### Factory Method ->Le patron Factory Method fournit une interface pour créer un objet qui laisse la possibilité aux sous-classes de décider quel type d'objet créer. Ce patron est utilisé lorsque la classe d'un objet n'est pas connue au moment de la compilation. Une méthode pour créer un objet factory method est définie dans une classe abstraite, et implémentée dans les différentes sous-classes. La factory method peut également comporter une implémentation par défaut. +> Le patron Factory Method fournit une interface pour créer un objet qui laisse la possibilité aux sous-classes de décider quel type d'objet créer. Ce patron est utilisé lorsque la classe d'un objet n'est pas connue au moment de la compilation. Une méthode pour créer un objet factory method est définie dans une classe abstraite, et implémentée dans les différentes sous-classes. La factory method peut également comporter une implémentation par défaut. ![Factory Method](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/factory-method.svg "Fig. 2") Considérons le code suivant : -```JavaScript +``` JavaScript myModule.config(function ($provide) { $provide.provider('foo', function () { var baz = 42; @@ -344,7 +350,7 @@ Chaque service, filtre ou contrôleur possède un "provider" qui est responsable Jetons un oeil sur l'implémentation d'AngularJS : -```JavaScript +``` JavaScript //... createInternalInjector(instanceCache, function(servicename) { @@ -388,7 +394,7 @@ function invoke(fn, self, locals, serviceName){ D'après ce code, nous remarquons que la méthode `$get` est utilisé à ce niveau : -```JavaScript +``` JavaScript instanceInjector.invoke(provider.$get, provider, undefined, servicename) ``` @@ -404,14 +410,13 @@ Utiliser le patron Factory Method offre des avantages dans notre cas grâce à l #### Decorator ->Ce patron permet d'attacher dynamiquement des responsabilités à un objet. Une alternative à l'héritage. Ce patron est inspiré des poupées russes. Un objet peut être caché à l'intérieur d'un autre objet décorateur qui lui rajoutera des fonctionnalités, l'ensemble peut être décoré avec un autre objet qui lui ajoute des fonctionnalités et ainsi de suite. Cette technique nécessite que l'objet décoré et ses décorateurs implémentent la même interface, qui est typiquement définie par une classe abstraite. +> Ce patron permet d'attacher dynamiquement des responsabilités à un objet. Une alternative à l'héritage. Ce patron est inspiré des poupées russes. Un objet peut être caché à l'intérieur d'un autre objet décorateur qui lui rajoutera des fonctionnalités, l'ensemble peut être décoré avec un autre objet qui lui ajoute des fonctionnalités et ainsi de suite. Cette technique nécessite que l'objet décoré et ses décorateurs implémentent la même interface, qui est typiquement définie par une classe abstraite. ![Decorator](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/decorator.svg "Fig. 4") AngularJS fournit de base un moyen d'étendre et d'enrichir les fonctionnalités des services existants. En utilisant la méthode `decorator` de l'objet `$provider`, il est possible de créer des "wrapper" de n'importe quel service définit ou fournit par un module tiers : - -```JavaScript +``` JavaScript myModule.controller('MainCtrl', function (foo) { foo.bar(); }); @@ -445,15 +450,12 @@ Ce patron est utile si nous souhaitons modifier le comportement des services tie #### Facade ->Ce patron fournit une interface unifiée sur un ensemble d'interfaces d'un système. Il est utilisé pour réaliser des interfaces de programmation. Si un sous-système comporte plusieurs composants qui doivent être utilisés dans un ordre précis, une classe façade sera mise à disposition, et permettra de contrôler l'ordre des opérations et de cacher les détails techniques des sous-systèmes. Une façade peut : - ->1. rendre une librairie plus simple à utiliser et à tester. - ->2. rendre une librairie plus simple à comprendre. - ->3. offrir une meilleure flexibilité dans le développement de la librairie. - ->4. englober une collection d'API mal conçues en une seule API bien conçue (en foncton des tâches). +> Ce patron fournit une interface unifiée sur un ensemble d'interfaces d'un système. Il est utilisé pour réaliser des interfaces de programmation. Si un sous-système comporte plusieurs composants qui doivent être utilisés dans un ordre précis, une classe façade sera mise à disposition, et permettra de contrôler l'ordre des opérations et de cacher les détails techniques des sous-systèmes. Une façade peut : +> +> 1. rendre une librairie plus simple à utiliser et à tester. +> 2. rendre une librairie plus simple à comprendre. +> 3. offrir une meilleure flexibilité dans le développement de la librairie. +> 4. englober une collection d'API mal conçues en une seule API bien conçue (en foncton des tâches). ![Facade](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/facade.svg "Fig. 11") @@ -461,27 +463,28 @@ Il existe peut de façades dans AngularJS. A chaque fois que vous voulez fournir Par exemple, regardons comment créer une requête POST avec `XMLHttpRequest` : - ```JavaScript +``` JavaScript var http = new XMLHttpRequest(), - url = '/example/new', - params = encodeURIComponent(data); + +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); + alert(http.responseText); } } + http.send(params); ``` Voici la même chose en utilisant le service `$http` d'AngularJS : -```JavaScript +``` javascript $http({ method: 'POST', url: '/example/new', @@ -491,9 +494,10 @@ $http({ alert(response); }); ``` + ou encore : -```JavaScript +``` JavaScript $http.post('/someUrl', data) .then(function (response) { alert(response); @@ -506,7 +510,7 @@ Le service `$resource` d'AngularJS est un service construit autour de `$http` et #### Proxy ->Ce patron est un substitut d'un objet, qui permet de contrôler l'utilisation de ce dernier. Un proxy est un objet destiné à protéger un autre objet. Le proxy a la même interface que l'objet à protéger. Il peut être créé par exemple pour permettre d’accéder à distance à un objet (via un middleware). Un proxy, dans sa forme la plus simple, ne protège rien du tout et transmet tous les appels de méthode à l'objet cible. +> Ce patron est un substitut d'un objet, qui permet de contrôler l'utilisation de ce dernier. Un proxy est un objet destiné à protéger un autre objet. Le proxy a la même interface que l'objet à protéger. Il peut être créé par exemple pour permettre d’accéder à distance à un objet (via un middleware). Un proxy, dans sa forme la plus simple, ne protège rien du tout et transmet tous les appels de méthode à l'objet cible. ![Proxy](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/proxy.svg "Fig. 9") @@ -520,7 +524,7 @@ Dans cette section, nous allons parler de l'implémentation du proxy virtuel dan Dans le code suivant, il y a un appel vers la méthode `get` de l'instance `$resource` référencée par `User` : -```JavaScript +``` JavaScript var User = $resource('/users/:id'), user = User.get({ id: 42 }); console.log(user); // {} @@ -530,36 +534,36 @@ L'appel de `console.log` affiche un objet vide. La requête AJAX, qui est émise Comment cela fonctionne dans AngularJS? Considérons le code suivant : - -```JavaScript +``` JavaScript function MainCtrl($scope, $resource) { var User = $resource('/users/:id'), $scope.user = User.get({ id: 42 }); } ``` -```html +``` html ``` ->**Pro tip** : Il n'est pas conseillé d'utiliser `$resource` directement dans un contrôleur. Préférez le mettre dans une factory ou un service ! +> **Pro tip** : Il n'est pas conseillé d'utiliser `$resource` directement dans un contrôleur. Préférez le mettre dans une factory ou un service ! Lorsque ce code s'exécute, la propriété `user` de l'objet `$scope` est initialement vide (`{}`), ce qui signifie que `user.name` sera `undefined` et rien ne sera rendu dans la vue. En interne, AngularJS garde une référence de cet objet vide. Lorsque le serveur répond à la requête GET, AngularJS met à jour l'objet avec les données reçues. Lors de l'itération suivante de la boucle du `$digest`, AngularJS détecte des changements dans l'objet `$scope.user`, ce qui déclenche le rafraichissement de la vue. #### Enregistrement Actif (Active Record) ->Active Record est une approche pour lire les données d'une base de données. Les attributs d'une table ou d'une vue sont encapsulés dans une classe. Ainsi l'objet, instance de la classe, est lié à un tuple de la base. L'objet Active Record encapsule donc les données ainsi que le comportement. +> Active Record est une approche pour lire les données d'une base de données. Les attributs d'une table ou d'une vue sont encapsulés dans une classe. Ainsi l'objet, instance de la classe, est lié à un tuple de la base. L'objet Active Record encapsule donc les données ainsi que le comportement. ![Active Record](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/active-record.svg "Fig. 7") AngularJS définit un service nommé `$resource` distribué dans un module additionnel. D'après la [documentation](https://docs.angularjs.org/api/ngResource/service/$resource) d'AngularJS du service `$resource` : ->Une factory pour la création d'objet permettant une interaction avec les données des sources RESTful. ->L'objet retourné possède des méthodes d'action offrant une abstraction très haut niveau et ne nécessitant pas une interaction avec le service `$http`. +> Une factory pour la création d'objet permettant une interaction avec les données des sources RESTful. +> +> L'objet retourné possède des méthodes d'action offrant une abstraction très haut niveau et ne nécessitant pas une interaction avec le service `$http`. Voici comment le service `$resource` peut être utilisé : -```JavaScript +``` JavaScript var User = $resource('/users/:id'), user = new User({ name: 'foo', @@ -573,7 +577,7 @@ l'appel à `$resource` retourne un constructeur permettant d'instancier des obje Le constructeur possède également des méthodes statiques équivalentes aux méthodes d'instances : -```JavaScript +``` JavaScript var user = User.get({ userid: userid }); ``` @@ -585,7 +589,7 @@ Vous pouvez trouver plus de détails concernant les pouvoirs du service `$resour #### Intercepting Filters ->Créé une chaîne de filtres composables afin d'implémenter des tâches de pré-processing et post-processing récurrentes lors de l'émission des requêtes. +> Créé une chaîne de filtres composables afin d'implémenter des tâches de pré-processing et post-processing récurrentes lors de l'émission des requêtes. ![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/intercepting-filters.svg "Fig. 3") @@ -597,7 +601,7 @@ L'objet `requestError` est un intercepteur qui est appelé lorsque l'intercepteu Voici un exemple basique : -```JavaScript +``` JavaScript $httpProvider.interceptors.push(function($q, dependency1, dependency2) { return { 'request': function(config) { @@ -614,16 +618,225 @@ $httpProvider.interceptors.push(function($q, dependency1, dependency2) { #### Composite +> Ce patron permet de composer une hiérarchie d'objets, et de manipuler de la même manière un élément unique, une branche, ou l'ensemble de l'arbre. Il permet en particulier de créer des objets complexes en reliant différents objets selon une structure en arbre. + +![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/composite.svg "Fig. 3") + +D'après le Gang of Four ([GoF](http://c2.com/cgi/wiki?GangOfFour)), le patron MVC n'est rien de plus que la combinaison de : + +- Strategy +- Composite +- Observer + +Ils explique que la vue est la composition de plusieurs composants. Dans AnguarlJS, nous sommes dans ce cas de figure. Les vues sont formées grâce à la composition des directives et les éléments DOM. + +Voyons cet exemple : + +``` HTML + + + + + + + Zippy! + + + +``` + +``` JavaScript +myModule.directive('zippy', function () { + return { + restrict: 'E', + template: '
', + link: function (scope, el) { + el.find('.header').click(function () { + el.find('.content').toggle(); + }); + } + } +}); +``` + +Dans cet exemple, nous avons définit une simple directive, qui se trouve être un composant de type UI. Ce composant appelé "zippy" possède un entête `div.header` et un contenu `div.content`. + +A partir du premier exemple, nous pouvons remarquer que tout l'arbre DOM est la composition de plusieurs éléments. L'élément racine est `html`, suivi juste après par `head`, ensuite `body`… etc. + +Dans le second exemple JavaScript, la propriété `template` de la directive contient une directive `ng-transclude` (sous forme d'attribut). Ceci signifie qu'à l'intérieur de la directive `zippy` nous avons une autre directive nommée `ng-transclude`. Autrement-dit, une composition de directive. + #### Interpreter +> Le patron comporte deux composants centraux: le contexte et l'expression ainsi que des objets qui sont des représentations d'éléments de grammaire d'un langage de programmation. Le patron est utilisé pour transformer une expression écrite dans un certain langage programmation - un texte source - en quelque chose de manipulable par programmation: Le code source est écrit conformément à une ou plusieurs règles de grammaire, et un objet est créé pour chaque utilisation d'une règles de grammaire. L'objet interpreter est responsable de transformer le texte source en objets. + +![Interpreter](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/interpreter.svg "Fig. 6") + +Grâce au service `$parse`, AngularJS propose son propre implémentation d'une DSL (Domain Specific Language). Cette DSL est une version très simplifiée du langage JavaScript. + +Voici les spécificités des expressions AngularJS : + +- elles peuvent contenir des filtre via la syntaxe `pipe` (à la manière d'UNIX) +- ne déclenche pas d'exceptions +- ne contient aucune structure de contrôle (même s'il est possible d'utiliser l'opérateur ternaire) +- sont évaluée dans le contexte du `$scope` de la vue + +Dans l'implémentation du service `$parse`, sont définis deux composants : + +``` JavaScript +//Responsable de convertir des caractères en token +var Lexer; +//Responsable d'interpréter les token et d'évaluder les expressions +var Parser; +``` + +Lorsqu'une expression a été transformée en `token`, elle est mise en cache pour des raisons de performance. + +Les symboles terminaux dans la DSL AngularJS sont définis comme ceci : + +``` JavaScript +var OPERATORS = { + /* jshint bitwise : false */ + 'null':function(){return null;}, + 'true':function(){return true;}, + 'false':function(){return false;}, + undefined:noop, + '+':function(self, locals, a,b){ + //... + }, + '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, + '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, + '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, + '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, + '=':noop, + '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, + '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, + '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, + '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, + '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, + '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, + '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, + '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, + '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, + '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, + '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, + '!':function(self, locals, a){return !a(self, locals);} +}; +``` + +Nous pouvons considérer chaque fonction associée à un symbole comme étant une implémentation de l'interface `AbstractExpression`. + +Chaque `Client` interprète une expression AngularJS donnée dans un contexte spécifique. + +Voici quelques exemples d'expressions AngularJS : + +``` JavaScript +// le filtre toUpperCase est appliqué au résultat de l'expression : +// (foo) ? bar : baz +(foo) ? bar : baz | toUpperCase +``` + #### Template View +> Affiche un rendu dans l'HTML, basé sur des marqueurs dans le code HTML. + +![Template View](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/template-view.svg "Fig. 8") + +La gestion du rendu dynamic d’une page n’est pas chose facile. Il est souvent question de beaucoup de manipulation de chaîne de caractères. Pour simplifier cette procédure, il est souvent plus simple d’incorporer des expressions dans notre HTML, ces expressions sont évaluées dans un contexte donné et tout le template est ensuite compilé vers le format final. Dans notre cas, ce format est HTML voire le DOM. C’est ce que font les moteurs de templating - ils prennent une DSL, l’évalue dans un contexte donné puis la transforme en un format souhaité. + +Les templates sont historiquement associés aux technologies back-end. Par exemple, vous pouvez embarquer du code PHP dans une page HTML pour créer une page dynamique, en utilisant un moteur de templating, tel que Smarty. + +En ce qui concerne JavaScript, il existe pléthore de moteurs de templating, les plus connus sont mustache.js et handlerbars. Ces moteurs manipulent souvent les templates sous format texte. Ces templates peuvent être mis directement dans la page statique, dans une balise `script` avec un type spécifique, récupéré depuis le serveur via `XHR` ou encore mis directement dans du JavaScript. + +Par exemple : + +``` html + +``` + +Le moteur de templating transforme ce texte (le contenu de la balise `script`) en éléments DOM en les compilant dans un contexte donné. Par exemple, si ce contenu est évalué dans le contexte suivant : `{ names: ['foo', 'bar', 'baz'] }` nous obtenant ceci : + +``` html +

Names

+ foo + bar + baz +``` + +En AngularJS, les templates n’ont pas besoin d’être compilés ; ils ne sont pas dans un format intermédiaire comme le template précédent. Ce que fait le compilateur AngularJS, il traverse l’arbre DOM à la recherche de directives connues (éléments, attributs, classes CSS ou encore des commentaires). Lorsqu’il rencontre l’une de ces directives, il invoque le code associé ce qui déclenche l’évaluation des différentes expressions dans le contexte courant. + +Par exemple : + +``` html +
    +
  • {{name}}
  • +
+``` + +L’évaluation de la directive `ng-reapeat`dans le contexte suivant : + +``` javascript +myCtrl.names = ['foo', 'bar', 'baz']; +``` + +produit le même résultat que précédemment : + +``` html +

Names

+ foo + bar + baz +``` + ### Scope #### Observer +> Ce patron établit une relation un à plusieurs entre des objets, où lorsqu'un objet change, plusieurs autres objets sont avisés du changement. Dans ce patron, un objet *le sujet* tient une liste des objets dépendants *les observateurs* qui seront avertis des modifications apportées au *sujet*. Quand une modification est apportée, le sujet emmet un message aux différents observateurs. + +![Observer](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/observer.svg "Fig. 7") + +Il existe deux façon pour communiquer entre les scopes (ou les contrôleurs) dans une application AngularJS. La première consiste à appeler les méthodes des parents depuis les scopes enfants. Ceci est rendu possible car le scope enfant hérite du prototype du scope parent, comme expliqué dans les sections précédentes (voir la section sur le [Scope](#scope)) ; Cette première façon de faire autorise un sens de communication : de l’enfant vers le parent. Cependant, il est souvent nécessaire d’appeler des méthodes d’un scope enfant, ou notifier un scope enfant suite à un événement déclenché dans le contexte du parent. AngularJS propose de base un patron Observer, qui permet ce genre de communication. + +Un autre cas d’usage de ce patron : lorsque plusieurs scopes sont concernés par un certain événement, qui est déclenché dans un autre contexte. Ceci permet en effet de découpler les scopes. + +Chaque `$scope` dans AngularJS possède trois méthodes : `$on`, `$emit`et `$broadcast`. La méthode `$on`accepte un topic et une fonction de callback. Cette callback peut être considérée comme un `observer`, un objet implémentant l’interface `Observer`: + +``` javascript +function ExampleCtrl($scope) { + $scope.$on('event-name', function handler() { + //body + }); +} +``` + +De cette manière, le `$scope`courant souscrit à un événement de type `event-name`. Lorsque cet événement est déclenché à n’importe quel niveau du `$scope` courant, que ce soit au niveau de ses parents ou ses enfants, la fonction `handler sera invoquée. + +Les méthodes `$emit`et `$broadcast`sont utilisées pour déclencher et propager les événements, vers `$scope` parents pour `$emit`et `$scope`enfants pour `$broadcast`. Voici un exemple : + +``` javascript +function ExampleCtrl($scope) { + $scope.$emit('event-name', { foo: 'bar' }); +} +``` + +Dans cet exemple, le `$scope`déclenche l’événement `event-name`et le propage vers tous les `$scope`parents. Ce qui signifie que chaque `$scope`parent ayant souscrit à `event-name` sera notifié et sa fonction `handler` sera appelée. La méthode `$broadcast`fonctionne de la même manière, à la différence près qu’elle propage l’événement vers tous les `$scope`enfants. + +Chaque `$scope`a la possibilité de souscrire plusieurs actions à un même événement. Dit autrement, il peut associer plusieurs observer à un même événement. + +Ce patron est également connu sous le nom de publish/subscribe (ou producteur/consommateur). + +Pour les bonnes pratiques concernant ce patron, voir [Le patron Observer en tant que Service externe](#observer-pattern-as-an-external-service) + #### Chain of Responsibilities + + #### Command ### Controllers @@ -674,5 +887,4 @@ $httpProvider.interceptors.push(function($q, dependency1, dependency2) { - -(WIP...) +(WIP...) \ No newline at end of file From 838c4459186d4597cccdf06e25d6e11b519d346e Mon Sep 17 00:00:00 2001 From: Wassim CHEGHAM Date: Wed, 22 Jul 2015 02:04:57 +0200 Subject: [PATCH 05/29] french translation (100%) --- i18n/README-fr-fr.md | 323 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 317 insertions(+), 6 deletions(-) diff --git a/i18n/README-fr-fr.md b/i18n/README-fr-fr.md index 63922fb..d0814d5 100644 --- a/i18n/README-fr-fr.md +++ b/i18n/README-fr-fr.md @@ -833,31 +833,342 @@ Ce patron est également connu sous le nom de publish/subscribe (ou producteur/c Pour les bonnes pratiques concernant ce patron, voir [Le patron Observer en tant que Service externe](#observer-pattern-as-an-external-service) -#### Chain of Responsibilities +#### Chaîne de responsabilité +> le patron de conception Chaîne de responsabilité permet à un nombre quelconque de classes d'essayer de répondre à une requête sans connaître les possibilités des autres classes sur cette requête. +![Chain of Responsibilities](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") + +Comme cité précédemment, les `$scopes`dans AngularJS forment une hiérarchie connue sous le nom de chaîne de scope. Certains d’entre eux peut être isolés, ce qui signifie qu’ils n’héritent pas des prototypes de leurs parents ; cependant, chaque `$scope`connait son parent direct grâce à la propriétés `$parent`qu'il contient. + +Lorsque les méthode `$emit`et `$broadcast`sont appelées, les `$scope`deviennent alors comme un bus d’événement, ou plus précisément, une chaine de responsabilité. Une fois l’événement est a été déclenché, que ce soit vers les parents ou les enfants, chaque `$scope`peut : + +- traiter l’événement et le passer au `$scope`suivant dans la chaîne. +- traiter l’événement et stopper sa propagation. +- passer directement l’événement au `$scope`suivant sans le traiter. +- stopper directement la propagation de l’événement. + +Dans l’exemple suivant, nous pouvons constater que `ChildCtrl` déclenche un événement, qui est propager vers le haut de la chaîne. Chaque `$scope`parent - celui créé par `ParentCtrl` et l’autre créé par `MainCtrl`- traite l’événement en affichant dans la console `"foo received"`. Si un `$scope`considère qu’il doit stopper la propagation de cet événement, il doit appeler la méthode `stopPropagation()`sur l’événement en question. + +Voici l’exemple de code : + +``` JavaScript +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'); +}); +``` + +Les `Handler`figurant dans le diagramme UML correspondent aux différents `$scope`injectés dans les contrôleurs. #### Command +> Ce patron emboîte une demande dans un objet, permettant de paramétrer, mettre en file d'attente, journaliser et annuler des demandes. + +![Command](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/command.svg "Fig. 11") + + + +Avant de rentrer dans les expliquation de ce patron de conception, étudions comment AngularJS implémente le `data biding`(ou liaison de données). + +Pour associer un modèle à une vue, nous utilisons la directive `ng-bind`, pour une liaison uni-directionnelle, et `ng-model`pour une liaison bi-directionnelle. Par exemple, si nous souhaitons que tous les changements du modèle soient reflétés dans la vue automatiquement : + +``` html + +``` + +A chaque fois que le modèle `foo` subit un changement, le contenu de la balise `span` sera mis à jour **automagiquement**. Voici un exemple avec une expression AngularJS : + +``` html + +``` + +Dans cet exemple, le contenu de la balise `span` sera le résultat de la concaténation des valeurs des modèles `foo`et `bar`. Mais que ce passe-t-il sous le capot ? Que fait AngularJS réellement ? + +Chaque `$scope`possède une méthode `$watch`. Lorsque le compilateur d'AngularJS traverse le DOM est rencontre une directive `ng-bind`, il créé un observateur (`watcher`) basé sur l’expression rencontrée : ion `foo + ' ' + bar | uppercase` comme ceci : + +``` Javascript +$scope.$watch("foo + ' ' + bar | uppercase", function update() { /* body */ }); +``` + +La callback `update`sera déclenchée à chaque fois que la valeur de l’expression vient à changer. Dans notre exemple, la callback met à jour le contenu de la balise `span`. + +Voici un aperçu du début de l’implémentation de la méthode `$watch`: + +``` javascript +$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 + }; +//... +``` + +Nous pouvons considérer l’objet `watcher` comme une commande. L’expression de la commande est évaluée à chaque itération de la boucle de digestion ou [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest). Lorsque AngularJS détecte un changement dans l’expression, la fonction `listener` est invoquée. La commande `watcher `encapsule tout le nécessaire pour observer une expression donnée, et déléguer l’exécution de la commande à la fonction `listener`, le `receiver` dans le diagramme UML. Le `$scope`quant à lui est l’équivalent du `Client` et la boucle de `$digest`est le `Invoker`. + ### Controllers -#### Page Controller +#### Contrôleur de page + +> Un objet qui traite la requête d’une page ou une action. — [Martin Fowler](https://en.wikipedia.org/wiki/Martin_Fowler) + +![Page Controller](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/page-controller.svg "Fig. 8") + +D’après le point [4](#references) un contrôleur de page est : + +> Le patron de conception Contrôleur de page, ou Page Controller, accepte une entrée depuis la requête, invoque l’action demandée d’un modèle, puis détermine la vue à utiliser pour la construction de la page finale. + +Il peut exister une certaine hiérarchie au sein des contrôleurs de page, puisqu’une page peut être construite en assemblant plusieurs blocs, tels que les headers, les footers, les blocs d’informations de session, etc. Dans AngularJS, nous avons des contrôleurs, mais avec beaucoup moins de responsabilités. Ils ne traitent pas les requêtes des utilisateurs, c’est la responsabilité des services `$router`ou `$state`et le rendu de la page est à la charge des directives `ng-view`ou `ui-view`. + +De la même manière que les Contrôleurs de page, les contrôleurs d’angulaires traite les interactions des utilisateurs, expose et mettent à jour les modèles. Le modèle est exposé à la vue, lorsqu’il est attaché au `$scope`; et toutes les méthodes invoquées dans les vues, en réponse aux interactions de l’utilisateur, sont également attachées au `$scope`. Une autre similitude avec les Contrôleurs de page, se situe au niveau de la hiérarchie qu’ils forment. Ces hiérarchie correspond à celle formée par la chaine des `$scope`. Ainsi, il est possible d’isoler des actions dans certains contrôleurs, ceux situés tout en haut de la chaîne. + +Voici un exemple illustrant cette hiérarchie : + +``` HTML + + + + + +
+ {{user.name}} + +
+ + +``` + +``` JavaScript +function MainCtrl($scope, $location, User) { + if (!User.isAuthenticated()) { + $location.path('/unauthenticated'); + } +} + +function ChildCtrl($scope, User) { + $scope.click = function () { + alert('Cliqué !'); + }; + $scope.user = User.get('123abc'); +} +``` + +Cet exemple (à ne pas utiliser dans un code en production !) a pour but d’illustrer l’utilisation d’un contrôleur de base `MainCtrl` dans lequel nous isolons la logique qui sera utilisée par les sous-contrôleur. Le contrôleur `ChildCtrl` est responsable de gérer les actions de l’utilisateur tel que la gestion des cliques sur le bouton, ainsi que d’exposer le modèle `user`à la vue en l’attachant au `$scope`. ### Others #### Module Pattern +Ce patron de conception ne figure ni dans le catalogue des [Gang of Four](http://c2.com/cgi/wiki?CategoryPattern), ni dans celui du [P of EAA](http://www.martinfowler.com/eaaCatalog/index.html). Par contre, c’est un patron de conception classique en JavaScript, dont le rôle est d’apporter une certaine forme d’encapsulation. + +En se basant sur le patron Module, il est possible de tirer avantage de la puissance des fermetures ([`closure`](http://wassimchegham.com/blog/javascript-pour-les-jedis-episode-2-attaque-des-closures-ou-fermetures)) et de la portée lexicale qui limite la visibilité des variables aux fonctions dans lesquelles elle ont été déclarées. + +Chaque module peut avoir zéro ou plusieurs membres privés, accessible uniquement dans la portée locale de la function. Cette fonction retourne un objet qui expose une API publique du module en question. Voici un exemple : + +``` javascript +var Page = (function () { + + var title; + + function setTitle(t) { + document.title = t; + title = t; + } + + function getTitle() { + return title; + } + + return { + setTitle: setTitle, + getTitle: getTitle + }; + +}()); +``` + +Dans cet exemple, nous avons déclaré une IIFE (Immediately-Invoked Function Expression), une fonction auto-invoquée qui, une fois invoquée retourne un objet avec deux méthodes : `setTitle()` et `getTitle()`. Cet objet est ensuite affecté à la variable `Page`. + +L’entité manipulant la variable `Page`n’a pas accès à la variable `title` par exemple, qui est définit dans la IIFE. + +Ce patron est également très intéressant lorsque nous devons définir des services en AngularJS. Nous pouvons rendre privée, une partie de la logique : + +``` javascript +app.factory('foo', function () { + + function privateMember() { + //body... + } + + function publicMember() { + //body... + privateMember(); + //body + } + + return { + publicMember: publicMember + }; +}); +``` + +Une fois le service `foo` injecté dans un composant, il ne nous sera pas possible d’utiliser directement les méthodes privées, mais uniquement celles exposées par l’objet retourné. Cette solution est extrêmement puissante surtout si nous développons une librairie. + ### Data Mapper -### Observer Pattern as an External Service +> Un Data Mapper est une couche d’accès au données qui réalise un transfert bi-directionnel entre la persistence de données (souvent une base relationnelle) et une représentation des données en mémoire (la couche métier). Le but de ce patron est de garder ces deux couches indépendantes les unes des autres, ainsi que du Data Mapper lui-même. + +![Data Mapper](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/data-mapper.svg "Fig. 10") + +Comme cité dans la définition, le Data Mapper est utilisé pour le transfert bi-directionnel de données entre la couche de persistence et la couche métier. Dans AngularJS, l’application communique avec un serveur exposant une API RESTFul. AngularJS propose le service `$resource`qui permet de communiquer avec le serveur dans un style [Active Record](#active-record). Souvent, les entités retournées par l’API ne correspondent toujours pas formatées de la façon qui nous arrange pour être directement exploitées. + +Prenons par exemple ce cas de figure, supposons que l’on dispose d’un modèle `User` avec les attributs suivants : + +- un nom. +- une adresse. +- une luiste d'amis + +Voici l’API exposant les ressources suivantes : + +- `GET /users/:id` : retourne le nom et l’adresse d’un utilisateur. +- `GET /friends/:id` : retourne la liste d’amis d’un utilisateur. + +Une première solution naïve consisterait à avoir deux services, un pour la première ressource et une autre pour la seconde. Mais ce serait mieux si nous avions qu’un seul et unique service, `User `par exemple : + +``` javascript +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('/users/' + 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; +}); +``` + +Avec cette solution, nous avons crée un pseudo Data Mapper, qui a pour rôle d’adapter l’API en fonction des contraintes de notre SPA. + +Nous pouvons utiliser le service `User` comme ceci : + +``` javascript +function MainCtrl($scope, User) { + User.get({ id: 1 }).then(function (data) { + $scope.user = data; + }); +} +``` + +Et la vue : + +``` html +
+
+ Name: {{user.name}} +
+
+ Address: {{user.address}} +
+
+ Friends with ids: +
    +
  • {{friend}}
  • +
+
+
+``` + + + +### Le patron Observer grâce à un Service Commun + +L’exemple de code a été récupéré depuis [ce projet](https://github.com/greglbd/angular-observer-pattern). Cet une factorie AngularJS qui créé un service implémentant le patron Observer. Ce patron fonctionne très bien avec la syntaxe `ControllerAs` et sert comme une alternative à `$scope.$watch()`ou encore `$scope.emit()`et `$scope.broadcast()`. + +Ce patron est utilisé pour faire communiquer plusieurs contrôleurs utilisant le même modèle. + +Voici un exemple démontrant comment attacher, notifier et détacher un événement grâce un Service Commun : + +``` javascript +angular.module('app.controllers') + .controller('ObserverExample', ObserverExample); + +ObserverExample.$inject= ['ObserverService', '$timeout']; + +function ObserverExample(ObserverService, $timeout) { + var vm = this; + var id = 'vm1'; + + ObserverService.attach(callbackFunction, 'let_me_know', id) + + function callbackFunction(params){ + console.log('now i know'); + ObserverService.detachByEvent('let_me_know') + } + + $timeout(function(){ + ObserverService.notify('let_me_know'); + }, 5000); +} +``` + +Alternative way to remove event + +``` javascript +angular.module('app.controllers') + .controller('ObserverExample', ObserverExample); +ObserverExample.$inject= ['ObserverService', '$timeout', '$scope']; + +function ObserverExample(ObserverService, $timeout, $scope) { + var vm = this; + var id = 'vm1'; + ObserverService.attach(callbackFunction, 'let_me_know', id) + + function callbackFunction(params){ + console.log('now i know'); + } + + $timeout(function(){ + ObserverService.notify('let_me_know'); + }, 5000); + + // Cleanup listeners when this controller is destroyed + $scope.$on('$destroy', function handler() { + ObserverService.detachByEvent('let_me_know') + }); +} +``` -##### About -##### Controller Example ## References -1. [Wikipedia](https://en.wikipedia.org/wiki). The source of all brief descriptions of the design patterns is wikipedia. +1. [Wikipedia](https://en.wikipedia.org/wiki). Toutes les définition des patrons de conception viennent de Wikipedia. 2. [AngularJS' documentation](https://docs.angularjs.org) 3. [AngularJS' git repository](https://github.com/angular/angular.js) 4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) From a505baa96bae406484439c628689e5d790688d39 Mon Sep 17 00:00:00 2001 From: Wassim CHEGHAM Date: Wed, 22 Jul 2015 02:08:58 +0200 Subject: [PATCH 06/29] typo --- i18n/README-fr-fr.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/i18n/README-fr-fr.md b/i18n/README-fr-fr.md index d0814d5..f66517a 100644 --- a/i18n/README-fr-fr.md +++ b/i18n/README-fr-fr.md @@ -112,8 +112,7 @@ Avec les expressions, les vues partielles définissent quelles actions doivent t Les contrôleurs d'AngularJS sont des fonctions JavaScript qui gèrent les interactions de l'utilisateur avec l'application web (par exemple, les événements de la souris, des touches claviers...etc), en attachant des méthodes au *scope*. Toues les dépendances externes d'un contrôleur sont fournit via le mécanisme d'ID ou injection de dépendances d'AngularJS. Les contrôleurs ont également pour but de fournir le *modèle* à la vue en attachant des données dans le *scope*. Nous pouvons voir ces données comme des *modèle vue* (*view model*) -``` -JavaScript +``` JavaScript function MyController($scope) { $scope.buttonText = 'Cliquer moi pour changer foo!'; $scope.foo = 42; @@ -149,7 +148,7 @@ L'héritage des scopes est illustré dans l'exemple suivant :
``` -``` +``` javascript function BaseCtrl($scope) { $scope.foo = function () { alert('Base foo'); From c8892588042ae79100aa1cf6f58356a9e37c8018 Mon Sep 17 00:00:00 2001 From: Wassim CHEGHAM Date: Wed, 22 Jul 2015 22:38:34 +0200 Subject: [PATCH 07/29] update toc --- i18n/README-fr-fr.md | 64 +++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/i18n/README-fr-fr.md b/i18n/README-fr-fr.md index f66517a..9d36dd5 100644 --- a/i18n/README-fr-fr.md +++ b/i18n/README-fr-fr.md @@ -1,43 +1,51 @@ -# Les patrons de conception avec AngularJS 1.x +# Les patrons de conception avec AngularJS + +## Table des matières -## Table des matières + + + -- [Traductions](#translations) +- [Traductions](#traductions) - [Abstract](#abstract) - [Introduction](#introduction) -- [Vue d'ensemble d'AngularJS](#angularjs-overview) - - [Les vues partielles](#partials) - - [Les contrôleurs](#controllers) - - [Le scope](#scope) - - [Les directives](#directives) - - [Les filtres](#filters) - - [Les services](#services) -- [Les patrons de conception d'AngularJS](#angularjs-patterns) - - [Les services](#services-1) - - [Singleton](#singleton) +- [Vue d'ensemble d'AngularJS](#vue-densemble-dangularjs) + - [Les vues partielles](#les-vues-partielles) + - [Les contrôleurs](#les-contr%C3%B4leurs) + - [Le scope](#le-scope) + - [Les directives](#les-directives) + - [Les filtres](#les-filtres) + - [Les services](#les-services) +- [Les patrons de conception d'AngularJS](#les-patrons-de-conception-dangularjs) + - [Les services](#les-services-1) + - [Le patron Singleton](#le-patron-singleton) - [Factory Method](#factory-method) - [Decorator](#decorator) - [Facade](#facade) - [Proxy](#proxy) - - [Active Record](#active-record) + - [Enregistrement Actif (Active Record)](#enregistrement-actif-active-record) - [Intercepting Filters](#intercepting-filters) - - [Les directives](#directives-1) + - [Les directives](#les-directives-1) - [Composite](#composite) - - [Interpreter](#interpreter) + - [Interpreter](#interpreter) - [Template View](#template-view) - - [Le scope](#scope-1) +- [Names](#names) +- [Names](#names-1) + - [Scope](#scope) - [Observer](#observer) - - [Chaîne de responsabilités](#chain-of-responsibilities) + - [Chaîne de responsabilité](#cha%C3%AEne-de-responsabilit%C3%A9) - [Command](#command) - - [Les contrôlleurs](#controller-1) - - [Page Controller](#page-controller) - - [Les autres patrons](#others) + - [Controllers](#controllers) + - [Contrôleur de page](#contr%C3%B4leur-de-page) + - [Others](#others) - [Module Pattern](#module-pattern) - - [Data Mapper](#data-mapper) - - [Le service en tant que Observer](#observer-pattern-as-an-external-service) -- [Références](#references) + - [Data Mapper](#data-mapper) + - [Le patron Observer grâce à un Service Commun](#le-patron-observer-gr%C3%A2ce-%C3%A0-un-service-commun) +- [References](#references) + + @@ -257,7 +265,7 @@ function MyCtrl(Developer) { } ``` -## AngularJS Patterns +## Les patrons de conception d'AngularJS Dans les sections suivantes, nous allons voir comment les patrons de conception traditionnels sont utilisés dans les composants d'AngularJS. @@ -474,7 +482,7 @@ http.setRequestHeader("Content-length", params.length); http.setRequestHeader("Connection", "close"); http.onreadystatechange = function () { if(http.readyState == 4 && http.status == 200) { - alert(http.responseText); + alert(http.responseText); } } @@ -999,7 +1007,7 @@ var Page = (function () { setTitle: setTitle, getTitle: getTitle }; - + }()); ``` @@ -1115,7 +1123,7 @@ Voici un exemple démontrant comment attacher, notifier et détacher un événem ``` javascript angular.module('app.controllers') - .controller('ObserverExample', ObserverExample); + .controller('ObserverExample', ObserverExample); ObserverExample.$inject= ['ObserverService', '$timeout']; From 77817c0f8d03651204752f742cfd7551a11b58aa Mon Sep 17 00:00:00 2001 From: Wassim CHEGHAM Date: Wed, 22 Jul 2015 22:38:34 +0200 Subject: [PATCH 08/29] update toc --- i18n/README-fr-fr.md | 118 ++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 69 deletions(-) diff --git a/i18n/README-fr-fr.md b/i18n/README-fr-fr.md index f66517a..a92bbbd 100644 --- a/i18n/README-fr-fr.md +++ b/i18n/README-fr-fr.md @@ -1,43 +1,49 @@ -# Les patrons de conception avec AngularJS 1.x - - +# Les patrons de conception avec AngularJS ## Table des matières -- [Traductions](#translations) -- [Abstract](#abstract) -- [Introduction](#introduction) -- [Vue d'ensemble d'AngularJS](#angularjs-overview) - - [Les vues partielles](#partials) - - [Les contrôleurs](#controllers) - - [Le scope](#scope) - - [Les directives](#directives) - - [Les filtres](#filters) - - [Les services](#services) -- [Les patrons de conception d'AngularJS](#angularjs-patterns) - - [Les services](#services-1) - - [Singleton](#singleton) - - [Factory Method](#factory-method) - - [Decorator](#decorator) - - [Facade](#facade) - - [Proxy](#proxy) - - [Active Record](#active-record) - - [Intercepting Filters](#intercepting-filters) - - [Les directives](#directives-1) - - [Composite](#composite) - - [Interpreter](#interpreter) - - [Template View](#template-view) - - [Le scope](#scope-1) - - [Observer](#observer) - - [Chaîne de responsabilités](#chain-of-responsibilities) - - [Command](#command) - - [Les contrôlleurs](#controller-1) - - [Page Controller](#page-controller) - - [Les autres patrons](#others) - - [Module Pattern](#module-pattern) - - [Data Mapper](#data-mapper) - - [Le service en tant que Observer](#observer-pattern-as-an-external-service) -- [Références](#references) + + + + + + +* [Traductions](#traductions) +* [Abstract](#abstract) +* [Introduction](#introduction) +* [Vue d'ensemble d'AngularJS](#vue-densemble-dangularjs) + * [Les vues partielles](#les-vues-partielles) + * [Les contrôleurs](#les-contr%C3%B4leurs) + * [Le scope](#le-scope) + * [Les directives](#les-directives) + * [Les filtres](#les-filtres) + * [Les services](#les-services) +* [Les patrons de conception d'AngularJS](#les-patrons-de-conception-dangularjs) + * [Les services](#les-services-1) + * [Le patron Singleton](#le-patron-singleton) + * [Factory Method](#factory-method) + * [Decorator](#decorator) + * [Facade](#facade) + * [Proxy](#proxy) + * [Enregistrement Actif (Active Record)](#enregistrement-actif-active-record) + * [Intercepting Filters](#intercepting-filters) + * [Les directives](#les-directives-1) + * [Composite](#composite) + * [Interpreter](#interpreter) + * [Template View](#template-view) + * [Scope](#scope) + * [Observer](#observer) + * [Chaîne de responsabilité](#cha%C3%AEne-de-responsabilit%C3%A9) + * [Command](#command) + * [Controllers](#controllers) + * [Contrôleur de page](#contr%C3%B4leur-de-page) + * [Others](#others) + * [Module Pattern](#module-pattern) + * [Data Mapper](#data-mapper) + * [Le patron Observer grâce à un Service Commun](#le-patron-observer-gr%C3%A2ce-%C3%A0-un-service-commun) +* [References](#references) + + @@ -257,7 +263,7 @@ function MyCtrl(Developer) { } ``` -## AngularJS Patterns +## Les patrons de conception d'AngularJS Dans les sections suivantes, nous allons voir comment les patrons de conception traditionnels sont utilisés dans les composants d'AngularJS. @@ -474,7 +480,7 @@ http.setRequestHeader("Content-length", params.length); http.setRequestHeader("Connection", "close"); http.onreadystatechange = function () { if(http.readyState == 4 && http.status == 200) { - alert(http.responseText); + alert(http.responseText); } } @@ -664,7 +670,7 @@ A partir du premier exemple, nous pouvons remarquer que tout l'arbre DOM est la Dans le second exemple JavaScript, la propriété `template` de la directive contient une directive `ng-transclude` (sous forme d'attribut). Ceci signifie qu'à l'intérieur de la directive `zippy` nous avons une autre directive nommée `ng-transclude`. Autrement-dit, une composition de directive. -#### Interpreter +### Interpreter > Le patron comporte deux composants centraux: le contexte et l'expression ainsi que des objets qui sont des représentations d'éléments de grammaire d'un langage de programmation. Le patron est utilisé pour transformer une expression écrite dans un certain langage programmation - un texte source - en quelque chose de manipulable par programmation: Le code source est écrit conformément à une ou plusieurs règles de grammaire, et un objet est créé pour chaque utilisation d'une règles de grammaire. L'objet interpreter est responsable de transformer le texte source en objets. @@ -999,7 +1005,7 @@ var Page = (function () { setTitle: setTitle, getTitle: getTitle }; - + }()); ``` @@ -1046,7 +1052,7 @@ Prenons par exemple ce cas de figure, supposons que l’on dispose d’un modèl Voici l’API exposant les ressources suivantes : -- `GET /users/:id` : retourne le nom et l’adresse d’un utilisateur. +- `GET /users/:id` : retourne le nom et l’adresse d’un utilisateur. - `GET /friends/:id` : retourne la liste d’amis d’un utilisateur. Une première solution naïve consisterait à avoir deux services, un pour la première ressource et une autre pour la seconde. Mais ce serait mieux si nous avions qu’un seul et unique service, `User `par exemple : @@ -1103,8 +1109,6 @@ Et la vue : ``` - - ### Le patron Observer grâce à un Service Commun L’exemple de code a été récupéré depuis [ce projet](https://github.com/greglbd/angular-observer-pattern). Cet une factorie AngularJS qui créé un service implémentant le patron Observer. Ce patron fonctionne très bien avec la syntaxe `ControllerAs` et sert comme une alternative à `$scope.$watch()`ou encore `$scope.emit()`et `$scope.broadcast()`. @@ -1115,7 +1119,7 @@ Voici un exemple démontrant comment attacher, notifier et détacher un événem ``` javascript angular.module('app.controllers') - .controller('ObserverExample', ObserverExample); + .controller('ObserverExample', ObserverExample); ObserverExample.$inject= ['ObserverService', '$timeout']; @@ -1174,27 +1178,3 @@ function ObserverExample(ObserverService, $timeout, $scope) { 5. [Patterns of Enterprise Application Architecture (P of EAA)](http://martinfowler.com/books/eaa.html) 6. [Using Dependancy Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) 7. [Why would one use the Publish/Subscribe pattern (in JS/jQuery)?](https://stackoverflow.com/questions/13512949/why-would-one-use-the-publish-subscribe-pattern-in-js-jquery) - - - - - - - - - - - - - - - - - - - - - - - -(WIP...) \ No newline at end of file From 1270e0457bf693bfd1c08fdde1146025d2126148 Mon Sep 17 00:00:00 2001 From: mgechev Date: Thu, 23 Jul 2015 17:30:12 +0300 Subject: [PATCH 09/29] Fix indentation --- gulpfile.js | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 3a57be7..0bc3bbd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -23,22 +23,22 @@ function genericTask(lang){ } return gulp.src(files) - .pipe(markdownpdf({ - cwd: path.resolve('./temp/'), - layout: 'github' - })) - .on('error', function(err){ - gutil.log(gutil.colors.red('doc task failed'), err); - }) - .pipe(rename(function (path) { - var lang = 'ENG'; - if(path.basename.indexOf('-') >= 0){ - lang = path.basename.replace('README-', '').toUpperCase(); - } - path.basename = TITLE + ' ('+lang+')'; - path.extname = '.pdf' - })) - .pipe(gulp.dest('./build/')); + .pipe(markdownpdf({ + cwd: path.resolve('./temp/'), + layout: 'github' + })) + .on('error', function(err){ + gutil.log(gutil.colors.red('doc task failed'), err); + }) + .pipe(rename(function (path) { + var lang = 'ENG'; + if(path.basename.indexOf('-') >= 0){ + lang = path.basename.replace('README-', '').toUpperCase(); + } + path.basename = TITLE + ' ('+lang+')'; + path.extname = '.pdf' + })) + .pipe(gulp.dest('./build/')); }); } @@ -64,15 +64,14 @@ gulp.task('default', function(cb){ gulp.task('copy:md', function(){ return gulp.src(['README.md', 'i18n/README-*.md']) - - // @todo I have no idea where should the TOC go?! - // for now, let's keep the TOC content and remove these markers - .pipe(replace('', '')) - .pipe(replace('', '')) - - // preapre the image paths for the renderer - .pipe(replace(/https:\/\/rawgit.com\/mgechev\/angularjs-in-patterns\/master\/images/g, '.')) - .pipe(gulp.dest('./temp/')); + // @todo I have no idea where should the TOC go?! + // for now, let's keep the TOC content and remove these markers + .pipe(replace('', '')) + .pipe(replace('', '')) + + // preapre the image paths for the renderer + .pipe(replace(/https:\/\/rawgit.com\/mgechev\/angularjs-in-patterns\/master\/images/g, '.')) + .pipe(gulp.dest('./temp/')); }); gulp.task('copy:images', function(){ From 2f688ad26d734506c86e2c950044ef665538121f Mon Sep 17 00:00:00 2001 From: mgechev Date: Thu, 23 Jul 2015 17:53:33 +0300 Subject: [PATCH 10/29] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 27c992d..f441d2a 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ - [Japanese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) by [morizotter](https://twitter.com/morizotter) - [Russian Translation](http://habrahabr.ru/post/250149/) +- [French Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) by [manekinekko](https://github.com/manekinekko) ## Abstract From b93debbdaa8a191bef7e5c0470a4492c71bdfd6f Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Thu, 17 Sep 2015 03:03:04 +1200 Subject: [PATCH 11/29] first commit of Chinese translation --- i18n/README-zh-cn.md | 1251 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1251 insertions(+) create mode 100644 i18n/README-zh-cn.md diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md new file mode 100644 index 0000000..0a682a2 --- /dev/null +++ b/i18n/README-zh-cn.md @@ -0,0 +1,1251 @@ +# AngularJS 模式 + + + +## 目录 + +* [译本](#translations) +* [摘要](#abstract) +* [介绍](#introduction) +* [AngularJS 概览](#angularjs-overview) + * [Partials](#partials) + * [Controllers](#controllers) + * [Scope](#scope) + * [Directives](#directives) + * [Filters](#filters) + * [Services](#services) +* [AngularJS 模式](#angularjs-patterns) + * [Services](#services-1) + * [单例模式 (Singleton)](#singleton) + * [工厂方法模式 (Factory Method)](#factory-method) + * [修饰模式](#decorator) + * [外观模式](#facade) + * [代理模式](#proxy) + * [Active Record](#active-record) + * [截取筛选器](#intercepting-filters) + * [Directives](#directives-1) + * [组合模式](#composite) + * [Interpreter](#interpreter) + * [Template View](#template-view) + * [Scope](#scope-1) + * [观察者模式](#observer) + * [责任链模式](#chain-of-responsibilities) + * [命令模式](#command) + * [Controller](#controller-1) + * [页面控制器](#page-controller) + * [其它](#others) + * [模块模式](#module-pattern) + * [数据映射模式](#data-mapper) + * [Observer Pattern as an External Service](#observer-pattern-as-an-external-service) +* [参考文献](#references) + + + +## 译本 + +- [日文](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) 译者:[morizotter](https://twitter.com/morizotter) +- [俄文](http://habrahabr.ru/post/250149/) +- [法文](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) 译者:[manekinekko](https://github.com/manekinekko) + +## 摘要 + +学习新事物的最好方式之一就是观察其如何运用整合已知的知识。本文将介绍基本的面向对象、设计模式和架构模式概念,而非向读者传授如何熟练使用设计或架构模式。本文的主旨是细述各种软件设计和架构模式是如何运用在 AngularJS 或者任何单页 AngularJS 应用中。 + + +## 介绍 + +本文将首先简要介绍 AngularJS 框架,解释其主要构件 - directives, filters, controllers, services, scope。第二部分会依照 AngularJS 构件组成顺序,分别列述 AngularJS 框架中所实现的各种设计和架构模式,其中会特别注出被多个构件使用的模式。 + + +文章最后会提及在 AngularJS 单页应用中常用的架构模式。 + + +## AngularJS 概览 + +AngularJS 是由 Google 开发的 JavaScript 框架,其提供了一个用于开发 CRUD 单页应用 (SPA) 的稳健基础。单页应用指的是一旦网页完成加载后,用户进行任何操作都无需重新加载完整的网页。这也意味着所有应用资源 (数据、模版、代码、样式) 都应该预先完成加载,或者更理想的是按需加载。由于大部分 CRUD 应用都包含共通的特质,AngularJS 提供了一套经过优化的现成工具实现来满足此需求。其中重要特质包括: + + +- 双向数据绑定 (two-way data binding) +- 依赖注入 (dependency injection) +- 关注点分离 (separation of concerns) +- 可测试性 (testability) +- 抽象化 (abstraction) + +实现关注点分离是将 AngularJS 应用划分为相互隔离的构件,如: + + +- partials +- controllers +- directives +- services +- filters + +这些构件可以在不同的模块中组合,以帮助实现更高层级的抽象化以及处理复杂事件。每个单独构件会封装应用程序中特定部分的逻辑。 + + +### Partials + +Partial (模版片段) 实际就是 HTML 字符串,其元素和属性中可能含有一些 AngularJS 表达式。与 mustache.js 和 handlebars 等框架相比,AngularJS 的一个不同之处就是其模版并非是一种需要转化为 HTML 的中间格式。 + + +每个单页应用都会在初始化时读取 `index.html` 文件。在 AngularJS 中,此文件包含有一套用来配置和启动应用程序的标准和自定义 HTML 属性、元素和注释。接下来每个用户操作将仅仅加载一个 partial 文件或者通过软件框架的数据绑定等方式改变应用的当前状态。 + + +**Partial 示例** + +```HTML + + + + + + + + + +``` + +Partial 文件可以通过 AngularJS 表达式来定义不同用户交互操作所对应的行为。例如在上面的示例中,`ng-click` 属性的值表示执行当前 *scope* 中的 `changeFoo` 函数。 + + +### Controllers + +AnuglarJS 中的 controller (控制器) 则是 JavaScript 函数,可以通过将函数绑定到对应的 *scope* 上, 来帮助处理用户与网页应用的交互操作 (例如鼠标或键盘事件等)。Controller 所需要的所有外部构件都是通过 AngularJS 的依赖注入 (Dependency Injection) 机制实现。Controller 还将数据绑定到 *scope* 上,从而给 partial 提供 *model* 功能。我们可以将这些数据看成是 *view model*。 + + +```JavaScript +function MyController($scope) { + $scope.buttonText = 'Click me to change foo!'; + $scope.foo = 42; + + $scope.changeFoo = function () { + $scope.foo += 1; + alert('Foo changed'); + }; +} +``` + +如果将以上 controller 示例与前一节中的 partial 示例结合在一起,用户就可以跟应用程序进行一些不同的交互操作。 + + +1. 通过输入框改变 `foo` 的值。由于这里使用了双向数据绑定,`foo` 值会立刻改变。 +2. 点击 `Click me to change foo!` 按钮来改变 `foo` 的值。 + + + +所有自定义的元素、属性、注释或类,只要被预先定义过,就都可以被 AngularJS 的 *directive* 识别。 + + +### Scope + +在 AngularJS 中,scope 是一个开放给 partial 的 JavaScript 对象。Scope 可以包含不同的属性 - 基本数据 (primitives)、对象和函数。所有归属于 scope 的函数都可以通过解析该 scope 所对应 partial 中的 AngularJS 表达式来执行,也可以由任何构件直接呼叫该函数,which keeps reference to the scope。附属于 scope 的数据可以通过使用合适的 *directives* 来绑定到视图 (view) 上,如此所有 partial 中的修改都会映射为某个 scope 属性的变化,反之亦然。 + + +AngularJS 应用中的 scope 还有另一个重要的特质,即它们都被连接到一条原型链 (prototypical chain) 上 (除了那些被表明为*独立 (isolated)* 的 scope)。在这种方式中,任何子 scope 都能呼叫属于其父母的函数,因为这些函数是该 scope 的直接或间接原型 (prototype) 的属性。 + + +以下示例展示了 scope 继承关系: + + +```HTML +
+
+ + +
+
+``` + +```JavaScript +function BaseCtrl($scope) { + $scope.foo = function () { + alert('Base foo'); + }; +} + +function ChildCtrl($scope) { + $scope.bar = function () { + alert('Child bar'); + }; +} +``` + +尽管 `div#child` 归属于 `ChildCtrl`,但由于 `ChildCtrl` 中所注入的 scope 通过原型继承了其父 scope (即 `BaseCtrl` 中注入的 scope),因此`button#parent-method` 可以接触到 `foo` 函数。 + + +### Directives + +在 AngularJS 中,所有 DOM 操作都应该放置在 directive 内。作为一个经验法则,每当你的 controller 里出现了 DOM 操作,你就应该创建一个新的 directive 或者考虑重构现有的 directive 用来处理 DOM 操作需求。每个 directive 都有其自己的名称和逻辑。最简化的 directive 仅仅包含名称和 *postLink* 函数定义,其中封装了所有该 directive 所需的逻辑。较复杂的 directive 可以包含很多属性,例如: + + +- template (模版) +- compile 函数 +- link 函数 +- 等等 + +通过引用 directive 的名称,这些属性可以被用于声明该 directive 的 partial 中。 + + +示例: + + +```JavaScript +myModule.directive('alertButton', function () { + return { + template: '', + scope: { + content: '@' + }, + replace: true, + restrict: 'E', + transclude: true, + link: function (scope, el) { + el.click(function () { + alert(scope.content); + }); + } + }; +}); +``` + +```HTML +Click me +``` + +在上述例子中,`` 标签会被按钮元素所替换。当用户点击按钮时,会弹出显示 `42` 的警告框。 + + +由于本文的关注点并非解释 AnuglarJS 的完整 API,directive 就解释到这里为止。 + + +### Filters + +AngularJS 中的 filter (过滤器) 负责封装进行数据格式化所需的逻辑。Filter 通常被用在 partial 中,但也可以通过依赖注入方式在 controller、directive、*service* 和其它 filter 中使用。 + + +以下是一个范例 filter 的定义,用来将给定的字符串转变为大写。 + + +```JavaScript +myModule.filter('uppercase', function () { + return function (str) { + return (str || '').toUpperCase(); + }; +}); +``` + +此 filter 可以用 Unix 的管道语法用于 partial 中。 + + +```HTML +
{{ name | uppercase }}
+``` + +在 controller 中,filter 可以按如下方式使用: + + +```JavaScript +function MyCtrl(uppercaseFilter) { + $scope.name = uppercaseFilter('foo'); //FOO +} +``` + +### Services + +所有其它逻辑,如果不属于以上所述构件,则应该放置到 service (服务) 中。Service 通常会封装领域专用逻辑 (domain specific logic)、持久层逻辑 (persistence logic)、XHR、WebSockets 等。当应用程序中 controller 变得过于臃肿是,就应该考虑将重复的代码放入一个 service 中。 + + +```JavaScript +myModule.service('Developer', function () { + this.name = 'Foo'; + this.motherLanguage = 'JavaScript'; + this.live = function () { + while (true) { + this.code(); + } + }; +}); +``` +Service 可以被注入到任何支持依赖注入机制的构件中,例如 controller、其它 service、filter 和 directive。 + + +```JavaScript +function MyCtrl(Developer) { + var developer = new Developer(); + developer.live(); +} +``` + +## AngularJS 模式 + +我们将在接下来的几节中探讨传统的设计和架构模式是如何在 AngularJS 的各个构件中组合实现的。并在最后一节讨论使用 AngularJS (或其它框架) 开发单页应用程序时常用的架构模式。 + + +### Services + +#### 单例模式 (Singleton) + +>The singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. + +In the UML diagram bellow is illustrated the singleton design pattern. + +![Singleton](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/singleton.svg "Fig. 1") + +When given dependency is required by any component, AngularJS resolves it using the following algorithm: + +- Takes its name and makes a lookup at a hash map, which is defined into a lexical closure (so it has a private visibility). +- If the dependency exists AngularJS pass it as parameter to the component, which requires it. +- If the dependency does not exists: + - AngularJS instantiate it by calling the factory method of its provider (i.e. `$get`). Note that instantiating the dependency may require recursive call to the same algorithm, for resolving all the dependencies required by the given dependency. This process may lead to circular dependency. + - AngularJS caches it inside the hash map mentioned above. + - AngularJS passes it as parameter to the component, which requires it. + +We can take better look at the AngularJS' source code, which implements the method `getService`: + +```JavaScript +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(); + } + } +} +``` + +We can think of each service as a singleton, because each service is instantiated no more than a single time. We can consider the cache as a singleton manager. There is a slight variation from the UML diagram illustrated above because instead of keeping static, private reference to the singleton inside its constructor function, we keep the reference inside the singleton manager (stated in the snippet above as `cache`). + +This way the services are actually singletons but not implemented through the Singleton pattern, which provides a few advantages over the standard implementation: + +- It improves the testability of your source code +- You can control the creation of singleton objects (in our case the IoC container controls it for us, by instantiating the singletons lazy) + +For further discussion on this topic Misko Hevery's [article](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) in the Google Testing blog could be considered. + +#### 工厂方法模式 (Factory Method) + +>The factory method pattern is a creational pattern, which uses factory methods to deal with the problem of creating objects without specifying the exact class of object that will be created. This is done by creating objects via a factory method, which is either specified in an interface (abstract class) and implemented in implementing classes (concrete classes); or implemented in a base class, which can be overridden when inherited in derived classes; rather than by a constructor. + +![Factory Method](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/factory-method.svg "Fig. 2") + +Lets consider the following snippet: + +```JavaScript +myModule.config(function ($provide) { + $provide.provider('foo', function () { + var baz = 42; + return { + //Factory method + $get: function (bar) { + var baz = bar.baz(); + return { + baz: baz + }; + } + }; + }); +}); + +``` + +In the code above we use the `config` callback in order to define new "provider". Provider is an object, which has a method called `$get`. Since in JavaScript we don't have interfaces and the language is duck-typed there is a convention to name the factory method of the providers that way. + +Each service, filter, directive and controller has a provider (i.e. object which factory method, called `$get`), which is responsible for creating the component's instance. + +We can dig a little bit deeper in AngularJS' implementation: + +```JavaScript +//... + +createInternalInjector(instanceCache, function(servicename) { + var provider = providerInjector.get(servicename + providerSuffix); + return instanceInjector.invoke(provider.$get, provider, undefined, servicename); +}, strictDi)); + +//... + +function invoke(fn, self, locals, serviceName){ + if (typeof locals === 'string') { + serviceName = locals; + locals = null; + } + + var args = [], + $inject = annotate(fn, strictDi, serviceName), + length, i, + key; + + for(i = 0, length = $inject.length; i < length; i++) { + key = $inject[i]; + if (typeof key !== 'string') { + throw $injectorMinErr('itkn', + 'Incorrect injection token! Expected service name as string, got {0}', key); + } + args.push( + locals && locals.hasOwnProperty(key) + ? locals[key] + : getService(key) + ); + } + if (!fn.$inject) { + // this means that we must be an array. + fn = fn[length]; + } + + return fn.apply(self, args); +} +``` + +From the example above we can notice how the `$get` method is actually used: + +```JavaScript +instanceInjector.invoke(provider.$get, provider, undefined, servicename) +``` + +The snippet above calls the `invoke` method of `instanceInjector` with the factory method (i.e. `$get`) of given service, as first argument. Inside `invoke`'s body `annotate` is called with first argument the factory method. Annotate resolves all dependencies through the dependency injection mechanism of AngularJS, which was considered above. When all dependencies are resolved the factory method is being called: `fn.apply(self, args)`. + +If we think in terms of the UML diagram above we can call the provider a "ConcreteCreator" and the actual component, which is being created a "Product". + +There are a few benefits of using the factory method pattern in this case, because of the indirection it creates. This way the framework can take care of the boilerplates during the instantiation of new components like: + +- The most appropriate moment, when the component needs to be instantiated +- Resolving all the dependencies required by the component +- The number of instances the given component is allowed to have (for services and filters only a single one but multiple for the controllers) + +#### Decorator + +>The decorator pattern (also known as Wrapper, an alternative naming shared with the Adapter pattern) is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. + +![Decorator](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/decorator.svg "Fig. 4") + +AngularJS provides out-of-the-box way for extending and/or enhancing the functionality of already existing services. Using the method `decorator` of `$provide` you can create "wrapper" of any service you have previously defined or used by a third-party: + +```JavaScript +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 example above defines new service called `foo`. In the `config` callback is called the method `$provide.decorator` with first argument `"foo"`, which is the name of the service, we want to decorate and second argument factory function, which implements the actual decoration. `$delegate` keeps reference to the original service `foo`. Using the dependency injection mechanism of AngularJS, reference to this local dependency is passed as first argument of the constructor function. +We decorate the service by overriding its method `bar`. The actual decoration is simply extending `bar` by invoking one more `console.log statement` - `console.log('Decorated');` and after that call the original `bar` method with the appropriate context. + +Using this pattern is especially useful when we need to modify the functionality of third party services. In cases when multiple similar decorations are required (like performance measurement of multiple methods, authorization, logging, etc.), we may have a lot of duplications and violate the DRY principle. In such cases it is useful to use [aspect-oriented programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming). The only AOP framework for AngularJS I'm aware of could be found at [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop). + +#### Facade + +>A facade is an object that provides a simplified interface to a larger body of code, such as a class library. A facade can: + +>1. make a software library easier to use, understand and test, since the facade has convenient methods for common tasks; + +>2. make the library more readable, for the same reason; + +>3. reduce dependencies of outside code on the inner workings of a library, since most code uses the facade, thus allowing more flexibility in developing the system; + +>4. wrap a poorly designed collection of APIs with a single well-designed API (as per task needs). + +![Facade](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/facade.svg "Fig. 11") + +There are a few facades in AngularJS. Each time you want to provide higher level API to given functionality you practically create a facade. + +For example, lets take a look how we can create an `XMLHttpRequest` POST request: + +```JavaScript +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); +``` +But if we want to post this data using the AngularJS' `$http` service we can: + +```JavaScript +$http({ + method: 'POST', + url: '/example/new', + data: data +}) +.then(function (response) { + alert(response); +}); +``` +or we can even: + +```JavaScript +$http.post('/someUrl', data) +.then(function (response) { + alert(response); +}); +``` +The second option provides pre-configured version, which creates a HTTP POST request to the given URL. + +Even higher level of abstraction is being created by `$resource`, which is build over the `$http` service. We will take a further look at this service in [Active Record](#active-record) and [Proxy](#proxy) sections. + +#### Proxy + +>A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. + +![Proxy](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/proxy.svg "Fig. 9") + +We can distinguish three different types of proxy: + +- Virtual Proxy +- Remote Proxy +- Protection Proxy + +In this sub-chapter we are going to take a look at AngularJS' implementation of Virtual Proxy. + +In the snippet bellow, there is a call to the `get` method of `$resource` instance, called `User`: + +```JavaScript +var User = $resource('/users/:id'), + user = User.get({ id: 42 }); +console.log(user); //{} +``` + +`console.log` would outputs an empty object. Since the AJAX request, which happens behind the scene, when `User.get` is invoked, is asynchronous, we don't have the actual user when `console.log` is called. Just after `User.get` makes the GET request it returns an empty object and keeps reference to it. We can think of this object as virtual proxy (a simple placeholder), which would be populated with the actual data once the client receives response by the server. + +How does this works with AngularJS? Well, lets consider the following snippet: + +```JavaScript +function MainCtrl($scope, $resource) { + var User = $resource('/users/:id'), + $scope.user = User.get({ id: 42 }); +} +``` + +```html + +``` +Initially when the snippet above executes, the property `user` of the `$scope` object will be with value an empty object (`{}`), which means that `user.name` will be undefined and nothing will be rendered. Internally AngularJS will keep reference to this empty object. Once the server returns response for the get request, AngularJS will populate the object with the data, received from the server. During the next `$digest` loop AngularJS will detect change in `$scope.user`, which will lead to update of the view. + +#### Active Record + +>The Active Record object is an object, which carries both data and behavior. Usually most of the data in these objects is persistent, responsibility of the Active Record object is to take care of the communication with the database in order to create, update, retrieve or delete the data. It may delegate this responsibility to lower level objects but calls to instance or static methods of the active record object cause the database communication. + +![Active Record](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/active-record.svg "Fig. 7") + +AngularJS defines a service called `$resource`. In the current version of AngularJS (1.2+) it is being distributed in module outside of the AngularJS' core. + +According to the AngularJS' documentation `$resource` is: + +>A factory which creates a resource object that lets you interact with RESTful server-side data sources. +>The returned resource object has action methods which provide high-level behaviors without the need to interact with the low level $http service. + +Here is how `$resource` could be used: + +```JavaScript +var User = $resource('/users/:id'), + user = new User({ + name: 'foo', + age : 42 + }); + +user.$save(); +``` + +The call of `$resource` will create a constructor function for our model instances. Each of the model instances will have methods, which could be used for the different CRUD operations. + +This way we can use the constructor function and its static methods by: + +```JavaScript +User.get({ userid: userid }); +``` + +The code above will immediately return an empty object and keep reference to it. Once the response have been successfully returned and parsed, AngularJS will populate this object with the received data (see [proxy](#proxy)). + +You can find more details for `$resource` [The magic of $resource](http://blog.mgechev.com/2014/02/05/angularjs-resource-active-record-http/) and [AngularJS' documentation](https://docs.angularjs.org/api/ngResource/service/$resource). + +Since Martin Fowler states that + +> responsibility of the Active Record object is to take care of the communication with the databse in order to create... + +`$resource` does not implements exactly the Active Record pattern, since it communicates with RESTful service instead of the database. Anyway, we can consider it as "Active Record like RESTful communication". + +#### Intercepting Filters + +>Create a chain of composable filters to implement common pre-processing and post-processing tasks during a Web page request. + +![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/intercepting-filters.svg "Fig. 3") + +In some cases you need to do some kind of pre and/or post processing of HTTP requests. In the case of the Intercepting Filters you pre/post process given HTTP request/response in order to include logging, security or any other concern, which is influenced by the request body or headers. Basically the Intercepting Filters pattern include a chain of filters, each of which process data in given order. The output of each filter is input of the next one. + +In AngularJS we have the idea of the Intercepting Filters in `$httpProvider`. `$httpProvider` has an array property called `interceptors`, which contains a list of objects. Each object may have properties called: `request`, `response`, `requestError`, `responseError`. + +`requestError` is an interceptor, which gets called when a previous interceptor threw an error or resolved with a rejection, respectively `responseError` is being called when the previous `response` interceptor has thrown an error. + +Here is a basic example how you can add interceptors using object literal: + +```JavaScript +$httpProvider.interceptors.push(function($q, dependency1, dependency2) { + return { + 'request': function(config) { + // same as above + }, + 'response': function(response) { + // same as above + } + }; +}); +``` + +### Directives + +#### Composite + +>The composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects are to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. + +![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/composite.svg "Fig. 3") + +According to the Gang of Four, MVC is nothing more than combination of: + +- Strategy +- Composite +- Observer + +They state that the view is composition of components. In AngularJS the situation is similar. Our views are formed by a composition of directives and DOM elements, on which these directives could be applied. + +Lets look at the following example: + +```HTML + + + + + + + Zippy! + + + +``` + +```JavaScript +myModule.directive('zippy', function () { + return { + restrict: 'E', + template: '
', + link: function (scope, el) { + el.find('.header').click(function () { + el.find('.content').toggle(); + }); + } + } +}); +``` + +This example defines a simple directive, which is a UI component. The defined component (called "zippy") has header and content. Click on its header toggles the visibility of the content. + +From the first example we can note that the whole DOM tree is a composition of elements. The root component is the `html` element, directly followed by the nested elements `head` and `body` and so on... + +In the second, JavaScript, example we see that the `template` property of the directive, contains markup with `ng-transclude` directive inside it. So this means that inside the directive `zippy` we have another directive called `ng-transclude`, i.e. composition of directives. Theoretically we can nest the components infinitely until we reach a leaf node. + +### Interpreter + +>In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence. + +![Interpreter](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/interpreter.svg "Fig. 6") + +Behind its `$parse` service, AngularJS provides its own implementation of interpreter of a DSL (Domain Specific Language). The used DSL is simplified and modified version of JavaScript. +The main differences between the JavaScript expressions and AngularJS expressions that AngularJS expressions: + +- may contain filters with UNIX like pipe syntax +- don't throw any errors +- don't have any control flow statements (exceptions, loops, if statements although you can use the ternary operator) +- are evaluated in given context (the context of the current `$scope`) + +Inside the `$parse` service are defined two main components: + +```JavaScript +//Responsible for converting given string into tokens +var Lexer; +//Responsible for parsing the tokens and evaluating the expression +var Parser; +``` + +Once given expression have been tokenized it is cached internally, because of performance concerns. + +The terminal expressions in the AngularJS DSL are defined as follows: + +```JavaScript +var OPERATORS = { + /* jshint bitwise : false */ + 'null':function(){return null;}, + 'true':function(){return true;}, + 'false':function(){return false;}, + undefined:noop, + '+':function(self, locals, a,b){ + //... + }, + '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, + '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, + '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, + '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, + '=':noop, + '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, + '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, + '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, + '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, + '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, + '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, + '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, + '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, + '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, + '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, + '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, + '!':function(self, locals, a){return !a(self, locals);} +}; +``` + +We can think of the function associated with each terminal as implementation of the `AbstractExpression`'s interface. + +Each `Client` interprets given AngularJS expression in a specific context - specific scope. + +Few sample AngularJS expressions are: + +```JavaScript +// toUpperCase filter is applied to the result of the expression +// (foo) ? bar : baz +(foo) ? bar : baz | toUpperCase +``` + +#### Template View + +> Renders information into HTML by embedding markers in an HTML page. + +![Template View](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/template-view.svg "Fig. 8") + +The dynamic page rendering is not that trivial thing. It is connected with a lot of string concatenations, manipulations and frustration. Far easier way to build your dynamic page is to write your markup and embed little expressions inside it, which are lately evaluated in given context and so the whole template is being compiled to its end format. In our case this format is going to be HTML (or even DOM). This is exactly what the template engines do - they take given DSL, evaluate it in the appropriate context and then turn it into its end format. + +Templates are very commonly used especially in the back-end. +For example, you can embed PHP code inside HTML and create a dynamic page, you can use Smarty or you can use eRuby with Ruby in order to embed Ruby code inside your static pages. + +For JavaScript there are plenty of template engines, such as mustache.js, handlebars, etc. Most of these engines manipulate the template as a string. The template could be located in different places - as static file, which is fetched with AJAX, as `script` embedded inside your view or even inlined into your JavaScript. + +For example: + +```html + +``` + +The template engine turns this string into DOM elements by compiling it within a given context. This way all the expressions embedded in the markup are evaluated and replaced by their value. + +For example if we evaluate the template above in the context of the following object: `{ names: ['foo', 'bar', 'baz'] }`, so we will get: + +```html +

Names

+ foo + bar + baz +``` + +AngularJS templates are actually HTML, they are not in an intermediate format like the traditional templates are. +What AngularJS compiler does is to traverse the DOM tree and look for already known directives (elements, attributes, classes or even comments). When AngularJS finds any of these directives it invokes the logic associated with them, which may involve evaluation of different expressions in the context of the current scope. + +For example: + +```html +
    +
  • {{name}}
  • +
+``` + +in the context of the scope: + +```javascript +$scope.names = ['foo', 'bar', 'baz']; +``` + +will produce the same result as the one above. The main difference here is that the template is not wrapped inside a `script` tag but is HTML instead. + + +### Scope + +#### Observer + +>The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems. + +![Observer](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/observer.svg "Fig. 7") + +There are two basic ways of communication between the scopes in an AngularJS application. The first one is calling methods of parent scope by a child scope. This is possible since the child scope inherits prototypically by its parent, as mentioned above (see [Scope](#scope)). This allows communication in a single direction - child to parent. Some times it is necessary to call method of given child scope or notify it about a triggered event in the context of the parent scope. AngularJS provides built-in observer pattern, which allows this. Another possible use case, of the observer pattern, is when multiple scopes are interested in given event but the scope, in which context the event is triggered, is not aware of them. This allows decoupling between the different scopes, non of the scopes should be aware of the rest of the scopes. + +Each AngularJS scope has public methods called `$on`, `$emit` and `$broadcast`. The method `$on` accepts topic as first argument and callback as second. We can think of the callback as an observer - an object, which implements the `Observer` interface (in JavaScript the functions are first-class, so we can provide only implementation of the `notify` method): + +```JavaScript +function ExampleCtrl($scope) { + $scope.$on('event-name', function handler() { + //body + }); +} +``` + +In this way the current scope "subscribes" to events of type `event-name`. When `event-name` is triggered in any parent or child scope of the given one, `handler` would be called. + +The methods `$emit` and `$broadcast` are used for triggering events respectively upwards and downwards through the scope chain. +For example: + +```JavaScript +function ExampleCtrl($scope) { + $scope.$emit('event-name', { foo: 'bar' }); +} +``` + +The scope in the example above, triggers the event `event-name` to all scopes upwards. This means that each of the parent scopes of the given one, which are subscribed to the event `event-name`, would be notified and their handler callback will be invoked. + +Analogical is the case when the method `$broadcast` is called. The only difference is that the event would be transmitted downwards - to all children scopes. +Each scope can subscribe to any event with multiple callbacks (i.e. it can associate multiple observers to given event). + +In the JavaScript community this pattern is better known as publish/subscribe. + +For a best practice example see [Observer Pattern as an External Service](#observer-pattern-as-an-external-service) + +#### Chain of Responsibilities + +>The chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain. + +![Chain of Responsibilities](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") + +As stated above the scopes in an AngularJS application form a hierarchy known as the scope chain. Some of the scopes are "isolated", which means that they don't inherit prototypically by their parent scope, but are connected to it via their `$parent` property. + +When `$emit` or `$broadcast` are called we can think of the scope chain as event bus, or even more accurately chain of responsibilities. Once the event is triggered it is emitted downwards or upwards (depending on the method, which was called). Each subsequent scope may: + +- Handle the event and pass it to the next scope in the chain +- Handle the event and stop its propagation +- Pass the event to the next scope in the chain without handling it +- Stop the event propagation without handling it + +In the example bellow you can see an example in which `ChildCtrl` triggers an event, which is propagated upwards through the scope chain. In the case above each of the parent scopes (the one used in `ParentCtrl` and the one used in `MainCtrl`) are going to handle the event by logging into the console: `"foo received"`. If any of the scopes should be considered as final destination it can call the method `stopPropagation` of the event object, passed to the callback. + +```JavaScript +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 different handlers from the UML diagram above are the different scopes, injected to the controllers. + +#### Command + +>In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters. + +![Command](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/command.svg "Fig. 11") + +Before continuing with the application of the command pattern lets describe how AngularJS implements data binding. + +When we want to bind our model to the view we use the directives `ng-bind` (for single-way data binding) and `ng-model` (for two-way data binding). For example, if we want each change in the model `foo` to reflect the view we can: + +```html + +``` + +Now each time we change the value of `foo` the inner text of the span will be changed. We can achieve the same effect with more complex AngularJS expressions, like: + +```html + +``` + +In the example above the value of the span will be the concatenated uppercased value of `foo` and `bar`. What happens behind the scene? + +Each `$scope` has method called `$watch`. When the AngularJS compiler find the directive `ng-bind` it creates a new watcher of the expression `foo + ' ' + bar | uppercase`, i.e. `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`. The callback will be triggered each time the value of the expression change. In the current case the callback will update the value of the span. + +Here are the first a couple of lines of the implementation of `$watch`: + +```javascript +$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 think of the `watcher` object as a command. The expression of the command is being evaluated on each [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) loop. Once AngularJS detects change in the expression, it invokes the `listener` function. The `watcher` command encapsulates the whole information required for watching given expression and delegates the execution of the command to the `listener` (the actual receiver). We can think of the `$scope` as the command's `Client` and the `$digest` loop as the command's `Invoker`. + +### Controllers + +#### Page Controller + +>An object that handles a request for a specific page or action on a Web site. Martin Fowler + +![Page Controller](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/page-controller.svg "Fig. 8") + +According to [4](#references) the page controller: + +>Page Controller pattern accept input from the page request, invoke the requested actions on the model, and determine the correct view to use for the resulting page. Separate the dispatching logic from any view-related code + +Since there is a lot of duplicate behavior between the different pages (like rendering footers, headers, taking care of the user's session, etc.) page controllers can form a hierarchy. In AngularJS we have controllers, which are with more limited scope of responsibilities. They don't accept user requests, since this is responsibility of the `$route` or `$state` services and the page rendering is responsibility of the directives `ng-view`/`ui-view`. + +Similarly to the page controllers, AngularJS controllers handle user interactions, provide and update the models. The model is exposed to the view when it is being attached to the scope, all methods invoked by the view, in result of user actions, are ones, which are already attached to the scope. Another similarity between the page controllers and the AngularJS controllers is the hierarchy, which they form. It corresponds to the scope hierarchy. That way common actions can be isolated to the base controllers. + +The controllers in AngularJS are quite similar to the code-behind in ASP.NET WebForms, since their responsibilities almost overlap. +Here is an example hierarchy between few controllers: + +```HTML + + + + + +
+ {{user.name}} + +
+ + +``` + +```JavaScript +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 aims to illustrates the most trivial way to reuse logic by using a base controller, anyway in production applications I don't recommend you to put your authorization logic in the controllers. The access to the different routes could be determined on a higher level of abstraction. + +The `ChildCtrl` is responsible for handling actions such as clicking the button with label `"Click"` and exposing the model to the view, by attaching it to the scope. + +### 其它 + +#### Module Pattern + +This is actually not a design pattern from Gang of Four, neither one from P of EAA. This is a traditional JavaScript pattern, which main goal is to provide encapsulation and privacy. + +Using the module pattern you can achieve privacy based on the JavaScript's functional lexical scope. Each module may have zero or more private members, which are hidden in the local scope of a function. This function returns an object, which exports the public API of the given module: + +```javascript +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 we have IIFE (Immediately-Invoked Function Expression), which after being called returns an object, with two methods (`setTitle` and `getTitle`). The returned object is being assigned to the `Page` variable. + +In this case the user of the `Page` object doesn't has direct access to the `title` variable, which is defined inside the local scope of the IIFE. + +The module pattern is very useful when defining services in AngularJS. Using this pattern we can simulate (and actually achieve) privacy: + +```javascript +app.factory('foo', function () { + + function privateMember() { + //body... + } + + function publicMember() { + //body... + privateMember(); + //body + } + + return { + publicMember: publicMember + }; +}); +``` + +Once we want to inject `foo` inside any other component we won't be able to use the private methods, but only the public ones. This solution is extremely powerful especially when one is building a reusable library. + +### Data Mapper + +>A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in memory data representation (the domain layer). The goal of the pattern is to keep the in memory representation and the persistent data store independent of each other and the data mapper itself. + +![Data Mapper](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/data-mapper.svg "Fig. 10") + +As the description above states, the data mapper is used for bidirectional transfer of data between a persistent data store and an in memory data representation. Usually our AngularJS application communicates with API server, which is written in any server-side language (Ruby, PHP, Java, JavaScript, etc.). + +Usually, if we have RESTful API `$resource` will help us communicate with the server in Active Record like fashion. Although, in some applications the data entities returned by the server are not in the most appropriate format, which we want to use in the front-end. + +For instance, lets assume we have application in which each user has: + +- name +- address +- list of friends + +And our API has the methods: + +- `GET /user/:id` - returns the user's name and the address of given user +- `GET /friends/:id` - returns the list of friends of given user + +Possible solution is to have two different services, one for the first method and one for the second one. Probably more useful solution would be if we have a single service called `User`, which loads the user's friends when we request the user: + +```javascript +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; +}); +``` + +This way we create pseudo-data mapper, which adapts our API according to the SPA requirements. + +We can use the `User` service by: + +```javascript +function MainCtrl($scope, User) { + User.get({ id: 1 }) + .then(function (data) { + $scope.user = data; + }); +} +``` + +And the following partial: + +```html +
+
+ Name: {{user.name}} +
+
+ Address: {{user.address}} +
+
+ Friends with ids: +
    +
  • {{friend}}
  • +
+
+
+``` + +### Observer Pattern as an External Service + +##### About + +Below is an example taken from [here](https://github.com/greglbd/angular-observer-pattern). This is an angular factory which creates a service implementing the Observer Pattern. It works well with the ControllerAs method of working as it can be much more efficient that `$scope.$watch` and more specific to a unique scope or object than $emit and $broadcast when used correctly. + +**Use Case:** You would use this pattern to communicate between 2 controllers that use the same model but are not connected in anyway. + +##### Controller Example + +Below example shows how to attach, notify and detach an event. + +```javascript +angular.module('app.controllers') + .controller('ObserverExample', ObserverExample); +ObserverExample.$inject= ['ObserverService', '$timeout']; + +function ObserverExample(ObserverService, $timeout) { + var vm = this; + var id = 'vm1'; + + ObserverService.attach(callbackFunction, 'let_me_know', id) + + function callbackFunction(params){ + console.log('now i know'); + ObserverService.detachByEvent('let_me_know') + } + + $timeout(function(){ + ObserverService.notify('let_me_know'); + }, 5000); +} +``` +Alternative way to remove event + +```javascript +angular.module('app.controllers') + .controller('ObserverExample', ObserverExample); +ObserverExample.$inject= ['ObserverService', '$timeout', '$scope']; + +function ObserverExample(ObserverService, $timeout, $scope) { + var vm = this; + var id = 'vm1'; + ObserverService.attach(callbackFunction, 'let_me_know', id) + + function callbackFunction(params){ + console.log('now i know'); + } + + $timeout(function(){ + ObserverService.notify('let_me_know'); + }, 5000); + + // Cleanup listeners when this controller is destroyed + $scope.$on('$destroy', function handler() { + ObserverService.detachByEvent('let_me_know') + }); +} +``` + +## 参考文献 + +1. [维基百科](https://en.wikipedia.org/wiki) 本文所有设计模式的简介都引自维基百科。 +2. [AngularJS 文档](https://docs.angularjs.org) +3. [AngularJS 源码库](https://github.com/angular/angular.js) +4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) +5. [企业应用架构模式 (P of EAA)](http://martinfowler.com/books/eaa.html) +6. [Using Dependancy Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) +7. [Why would one use the Publish/Subscribe pattern (in JS/jQuery)?](https://stackoverflow.com/questions/13512949/why-would-one-use-the-publish-subscribe-pattern-in-js-jquery) From d560b40e6d8049aad78250936f9bf3faa3184b3d Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Thu, 17 Sep 2015 11:57:51 +1200 Subject: [PATCH 12/29] Finish translating Service-Singleton section --- i18n/README-zh-cn.md | 46 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 0a682a2..5b63d19 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -18,7 +18,7 @@ * [Services](#services-1) * [单例模式 (Singleton)](#singleton) * [工厂方法模式 (Factory Method)](#factory-method) - * [修饰模式](#decorator) + * [修饰模式 (Decorator)](#decorator) * [外观模式](#facade) * [代理模式](#proxy) * [Active Record](#active-record) @@ -171,12 +171,12 @@ All the custom elements, attributes, comments or classes could be recognized as ### Scope -在 AngularJS 中,scope 是一个开放给 partial 的 JavaScript 对象。Scope 可以包含不同的属性 - 基本数据 (primitives)、对象和函数。所有归属于 scope 的函数都可以通过解析该 scope 所对应 partial 中的 AngularJS 表达式来执行,也可以由任何构件直接呼叫该函数,which keeps reference to the scope。附属于 scope 的数据可以通过使用合适的 *directives* 来绑定到视图 (view) 上,如此所有 partial 中的修改都会映射为某个 scope 属性的变化,反之亦然。 +在 AngularJS 中,scope 是一个开放给 partial 的 JavaScript 对象。Scope 可以包含不同的属性 - 基本数据 (primitives)、对象和函数。所有归属于 scope 的函数都可以通过解析该 scope 所对应 partial 中的 AngularJS 表达式来执行,也可以由任何构件直接调用该函数,which keeps reference to the scope。附属于 scope 的数据可以通过使用合适的 *directives* 来绑定到视图 (view) 上,如此所有 partial 中的修改都会映射为某个 scope 属性的变化,反之亦然。 -AngularJS 应用中的 scope 还有另一个重要的特质,即它们都被连接到一条原型链 (prototypical chain) 上 (除了那些被表明为*独立 (isolated)* 的 scope)。在这种方式中,任何子 scope 都能呼叫属于其父母的函数,因为这些函数是该 scope 的直接或间接原型 (prototype) 的属性。 +AngularJS 应用中的 scope 还有另一个重要的特质,即它们都被连接到一条原型链 (prototypical chain) 上 (除了那些被表明为*独立 (isolated)* 的 scope)。在这种方式中,任何子 scope 都能调用属于其父母的函数,因为这些函数是该 scope 的直接或间接原型 (prototype) 的属性。 @@ -328,6 +328,7 @@ myModule.service('Developer', function () { }; }); ``` + Service 可以被注入到任何支持依赖注入机制的构件中,例如 controller、其它 service、filter 和 directive。 +以下 UML 图展示了单例设计模式。 + ![Singleton](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/singleton.svg "Fig. 1") +AngularJS 会按照以下算法来解决构件所需要的依赖关系: + +- 提取所依赖组件的名称,并查询哈希表 (该表是定义在一个闭包中,所以外部不可见)。 +- 如果此依赖组件已经存在于应用中,AngularJS 会在需要的时候以参数的形式将其传递给对应的构件。 +- 如果所依赖的组件不存在: + - AngularJS 首先会调用其提供者的生成函数,如 `$get`。值得注意的是,创建此依赖组件的实例时,可能会对算法内容进行递归调用,以解决该组件本事的依赖关系。 + - AngularJS 然后会将其缓存在上面提到的哈希表中。 + - AngularJS 最后会在需要的时候将其传递给对应组件。 + +以 AngularJS 源代码中 `getService` 函数的实现为例: + ```JavaScript function getService(serviceName) { @@ -394,14 +415,27 @@ function getService(serviceName) { } ``` +由于每个 service 只会被实例化一次,我们可以将每个 service 看成是一个单例。缓存则可以被认为是单例管理器。这里与上面展示的 UML 图有微小的区别,那就是我们并不将单例的静态私有的 reference 保存在其构造 (constructor) 函数中,而是将 reference 保存在单例管理器中 (以上代码中的 `cache`)。 + +如此 service 实际还是单例,但并不是以传统单例设计模式的方法所实现。相比之下,这种方式有如下优点: + +- 增强代码的可测试性 +- 控制单例对象的创建 (在本节例子中,IoC 容器通过懒惰式单例实例化方式帮我们进行控制) + +对于更深入的讨论,可以参考 Misko Hevery 在 Google Testing blog 上的[文章](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html)。 + #### 工厂方法模式 (Factory Method) @@ -493,7 +527,7 @@ There are a few benefits of using the factory method pattern in this case, becau - Resolving all the dependencies required by the component - The number of instances the given component is allowed to have (for services and filters only a single one but multiple for the controllers) -#### Decorator +#### 修饰模式 (Decorator) >The decorator pattern (also known as Wrapper, an alternative naming shared with the Adapter pattern) is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. From 90d2c4f2aef81702e97f486cc0cfdb4fa912288d Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Thu, 17 Sep 2015 15:03:02 +1200 Subject: [PATCH 13/29] Chinese translate - 'factory method' and 'decorator' section --- i18n/README-zh-cn.md | 51 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 5b63d19..3d1b95e 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -377,6 +377,7 @@ When given dependency is required by any component, AngularJS resolves it using - AngularJS 首先会调用其提供者的生成函数,如 `$get`。值得注意的是,创建此依赖组件的实例时,可能会对算法内容进行递归调用,以解决该组件本事的依赖关系。 - AngularJS 然后会将其缓存在上面提到的哈希表中。 - AngularJS 最后会在需要的时候将其传递给对应组件。 + -#### 工厂方法模式 (Factory Method) +#### 工厂方法模式 (Factory Method) +>工厂方法模式是一种创建型模式,其实现了一个“工厂方法”概念来处理在不指定对象具体类型的情况下创建对象的问题。其解决方式不是通过构造函数来完成,而是在接口 (abstract class) 中定义一个用来创建对象的工厂方法,然后在实现类 (concrete classes) 中实现它。或者在一个基础类 (base class) 中实现它,而该类又可以通过继承关系被派生类 (derived class) 所重写。 + ![Factory Method](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/factory-method.svg "Fig. 2") +用如下代码为例: + ```JavaScript myModule.config(function ($provide) { @@ -463,11 +471,20 @@ myModule.config(function ($provide) { ``` +在上面的代码中,我们使用 `config` 回调来定义一个新的“provider”。Provider 是一个对象,其中含有一个 `$get` 函数。由于 JavaScript 语言没有接口 (interface),而语言本身是鸭子类型 (duck-typed),所以这里提供了一个给 provider 命名工厂方法的规则。 + +每个 service、filter、directive 和 controller 都包含一个 provider (即工厂方法名为 `$get` 的对象),用于负责创建该组件的实例。 + +让我们更深入的来看看 AngularJS 中是如何实现的: + ```JavaScript //... @@ -511,29 +528,52 @@ function invoke(fn, self, locals, serviceName){ } ``` +从以上例子中,我们注意到 `$get` 函数被下面的代码使用: + ```JavaScript instanceInjector.invoke(provider.$get, provider, undefined, servicename) ``` +上面这个代码片段调用了 `instanceInjector` 的 `invoke` 函数,其中第一个参数就是某服务的工厂方法 (即 `$get`)。在 `invoke` 内部,`annotate` 函数又将该工厂方法作为其第一个参数。如代码所示,`annotate` 使用 AngularJS 的依赖注入机制来解决所有依赖关系。当所有依赖关系都满足后,工厂方法函数就会被调用:`fn.apply(self, args)`。 + +如果对照上面的 UML 图来看,我们可以认为这里的 provider 就是图中的“ConcreteCreator”,而实际组件就是“Product”。 + +由于工厂方法能间接的创建对象,使用这种设计模式能带来一些益处。尤其是框架能够在组件实例化的过程中关注解决一些 boilerplate: + + +- 最恰当的时候来完成组件所需的实例化过程 +- 解决组件所需的所有依赖关系 +- 给定组件所允许存在的实例个数 (对于 service 和 filter 来说只有一个,而 controller 可以有多个实例) + #### 修饰模式 (Decorator) - +>修饰模式又被称为 wrapper,与 Adapter 模式的别名一样。它是一种可以动态或静态的往一个独立对象中添加新的行为,而不影响同一类所生成的其它对象的行为的设计模式。 + ![Decorator](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/decorator.svg "Fig. 4") +AngularJS 已经提供了这种方式来扩展/增强现有 service 的功能。在这里通过使用 `$provide` 的 `decorator` 函数,我们可以在第三方已经定义和使用的 service 上创建一个“wrapper”: + ```JavaScript myModule.controller('MainCtrl', function (foo) { @@ -562,10 +602,17 @@ myModule.config(function ($provide) { }); }); ``` + +上述例子定义了一个名为 `foo` 的新 service。在 `config` 中,回调了 `$provide.decorator` 函数,其第一个参数 `“foo”` 就是我们想要修饰的 service 的名称,而第二个参数则是实现修饰内容的函数。`$delegate` 则保持引用原有 `foo` service。通过使用 AngularJS 的依赖注入机制,这个本地依赖的 reference 是以构造函数的第一个参数传递。在这里,我们对 service 的修饰是重写其 `bar` 函数。实际修饰内容只是多执行一条 `console.log 语句` - `console.log('Decorated');`,然后继续在对应的上下文中调用原有 `bar` 函数。 + +在需要修改第三方 service 的功能时,使用这种模式特别有用。如果需要多个类似功能的修饰时 (例如函数的性能测量,授权,日志记录等),我们可能会生成大量重复的代码,违反 DRY 原则。这种情况就需要使用[面向侧面的程序设计 (aspect-oriented programming)](http://en.wikipedia.org/wiki/Aspect-oriented_programming)。目前我所知的 AngularJS 的唯一 AOP 框架是 [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop)。 + #### Facade From 22649c9e212f503503b9e6dc5deb6127b7f944b2 Mon Sep 17 00:00:00 2001 From: mgechev Date: Thu, 17 Sep 2015 15:49:59 +0300 Subject: [PATCH 14/29] Fix #23 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f441d2a..88450d8 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ * [Intercepting Filters](#intercepting-filters) * [Directives](#directives-1) * [Composite](#composite) - * [Interpreter](#interpreter) + * [Interpreter](#interpreter) * [Template View](#template-view) * [Scope](#scope-1) * [Observer](#observer) @@ -663,7 +663,7 @@ From the first example we can note that the whole DOM tree is a composition of e In the second, JavaScript, example we see that the `template` property of the directive, contains markup with `ng-transclude` directive inside it. So this means that inside the directive `zippy` we have another directive called `ng-transclude`, i.e. composition of directives. Theoretically we can nest the components infinitely until we reach a leaf node. -### Interpreter +#### Interpreter >In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence. @@ -1016,7 +1016,7 @@ app.factory('foo', function () { Once we want to inject `foo` inside any other component we won't be able to use the private methods, but only the public ones. This solution is extremely powerful especially when one is building a reusable library. -### Data Mapper +#### Data Mapper >A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in memory data representation (the domain layer). The goal of the pattern is to keep the in memory representation and the persistent data store independent of each other and the data mapper itself. @@ -1092,7 +1092,7 @@ And the following partial: ``` -### Observer Pattern as an External Service +#### Observer Pattern as an External Service ##### About From 034f031492d3a763629122b3cf3ff57a7ab42135 Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Fri, 18 Sep 2015 02:01:55 +1200 Subject: [PATCH 15/29] Finish Chinse translation for Services section --- i18n/README-zh-cn.md | 178 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 143 insertions(+), 35 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 3d1b95e..7e1800f 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -19,37 +19,38 @@ * [单例模式 (Singleton)](#singleton) * [工厂方法模式 (Factory Method)](#factory-method) * [修饰模式 (Decorator)](#decorator) - * [外观模式](#facade) - * [代理模式](#proxy) - * [Active Record](#active-record) - * [截取筛选器](#intercepting-filters) + * [外观模式 (Facade)](#facade) + * [代理模式 (Proxy)](#proxy) + * [Active Record 模式](#active-record) + * [截取筛选器 (Intercepting Filters)](#intercepting-filters) * [Directives](#directives-1) - * [组合模式](#composite) - * [Interpreter](#interpreter) - * [Template View](#template-view) + * [组合模式 (Composite)](#composite) + * [解释器模式 (Interpreter)](#interpreter) + * [模版视图模式 (Template View)](#template-view) * [Scope](#scope-1) - * [观察者模式](#observer) - * [责任链模式](#chain-of-responsibilities) - * [命令模式](#command) + * [观察者模式 (Observer)](#observer) + * [责任链模式 (Chain of Responsibilities)](#chain-of-responsibilities) + * [命令模式 (Command)](#command) * [Controller](#controller-1) - * [页面控制器](#page-controller) + * [页面控制器 (Page Controller)](#page-controller) * [其它](#others) - * [模块模式](#module-pattern) - * [数据映射模式](#data-mapper) - * [Observer Pattern as an External Service](#observer-pattern-as-an-external-service) + * [模块模式 (Module Pattern)](#module-pattern) + * [数据映射模式 (Data Mapper)](#data-mapper) + * [观察者模式作为外部服务 (Observer Pattern as an External Service)](#observer-pattern-as-an-external-service) * [参考文献](#references) ## 译本 -- [日文](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) 译者:[morizotter](https://twitter.com/morizotter) -- [俄文](http://habrahabr.ru/post/250149/) -- [法文](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) 译者:[manekinekko](https://github.com/manekinekko) +- [英文原文](https://github.com/mgechev/angularjs-in-patterns/blob/master/README.md) 作者:[mgechev](https://github.com/mgechev) +- [日文翻译](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) 译者:[morizotter](https://twitter.com/morizotter) +- [法文翻译](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) 译者:[manekinekko](https://github.com/manekinekko) +- [俄文翻译](http://habrahabr.ru/post/250149/) ## 摘要 -学习新事物的最好方式之一就是观察其如何运用整合已知的知识。本文将介绍基本的面向对象、设计模式和架构模式概念,而非向读者传授如何熟练使用设计或架构模式。本文的主旨是细述各种软件设计和架构模式是如何运用在 AngularJS 或者任何单页 AngularJS 应用中。 +学习新事物的最好方式之一就是观察其如何运用整合已知的知识。本文将介绍基本的面向对象、设计模式和架构模式概念,而非向读者传授如何熟练使用这些设计或架构模式。本文的主旨是细述各种软件设计和架构模式是如何运用在 AngularJS 或者任何单页 AngularJS 应用中。 @@ -552,9 +553,9 @@ If we think in terms of the UML diagram above we can call the provider a "Concre There are a few benefits of using the factory method pattern in this case, because of the indirection it creates. This way the framework can take care of the boilerplates during the instantiation of new components like: --> -- 最恰当的时候来完成组件所需的实例化过程 +- 选择最恰当的时机来完成组件所需的实例化过程 - 解决组件所需的所有依赖关系 -- 给定组件所允许存在的实例个数 (对于 service 和 filter 来说只有一个,而 controller 可以有多个实例) +- 设定组件所允许存在的实例个数 (对于 service 和 filter 来说只有一个,而 controller 可以有多个实例) #### 修饰模式 (Decorator) ->修饰模式又被称为 wrapper,与 Adapter 模式的别名一样。它是一种可以动态或静态的往一个独立对象中添加新的行为,而不影响同一类所生成的其它对象的行为的设计模式。 +>修饰模式又被称为 wrapper,与适配器模式的别名一样。它是一种可以动态或静态的往一个独立对象中添加新行为,而不影响同一个类所生成的其它对象的行为的设计模式。 ![Decorator](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/decorator.svg "Fig. 4") -AngularJS 已经提供了这种方式来扩展/增强现有 service 的功能。在这里通过使用 `$provide` 的 `decorator` 函数,我们可以在第三方已经定义和使用的 service 上创建一个“wrapper”: +AngularJS 已经提供了这种方式来扩展/增强现有 service 的功能。下面通过使用 `$provide` 的 `decorator` 函数,我们可以在第三方现有的 service 上创建一个“wrapper”: @@ -603,19 +604,30 @@ myModule.config(function ($provide) { }); ``` -上述例子定义了一个名为 `foo` 的新 service。在 `config` 中,回调了 `$provide.decorator` 函数,其第一个参数 `“foo”` 就是我们想要修饰的 service 的名称,而第二个参数则是实现修饰内容的函数。`$delegate` 则保持引用原有 `foo` service。通过使用 AngularJS 的依赖注入机制,这个本地依赖的 reference 是以构造函数的第一个参数传递。在这里,我们对 service 的修饰是重写其 `bar` 函数。实际修饰内容只是多执行一条 `console.log 语句` - `console.log('Decorated');`,然后继续在对应的上下文中调用原有 `bar` 函数。 +上述例子定义了一个名为 `foo` 的新 service。在 `config` 中,回调了 `$provide.decorator` 函数,其第一个参数 `“foo”` 就是我们想要修饰的 service 的名称,而第二个参数则是实现修饰内容的函数。`$delegate` 则保持引用原有 `foo` service。通过使用 AngularJS 的依赖注入机制,这个本地依赖的引用 (reference) 是以构造函数的第一个参数传递。在这里,我们对 service 的修饰是重写其 `bar` 函数。实际修饰内容只是多执行一条 `console.log` 语句 - `console.log('Decorated');`,然后继续在对应的上下文中调用原有 `bar` 函数。 -在需要修改第三方 service 的功能时,使用这种模式特别有用。如果需要多个类似功能的修饰时 (例如函数的性能测量,授权,日志记录等),我们可能会生成大量重复的代码,违反 DRY 原则。这种情况就需要使用[面向侧面的程序设计 (aspect-oriented programming)](http://en.wikipedia.org/wiki/Aspect-oriented_programming)。目前我所知的 AngularJS 的唯一 AOP 框架是 [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop)。 +在需要修改第三方 service 的功能时,使用这种模式特别有用。如果需要使用多个类似功能的修饰 (例如函数的性能测量,授权,日志记录等),我们可能会生成大量重复的代码,因而违反 DRY 原则。这种情况就需要使用[面向侧面的程序设计 (aspect-oriented programming)](http://en.wikipedia.org/wiki/Aspect-oriented_programming)。目前我所知的 AngularJS 的唯一 AOP 框架是 [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop)。 -#### Facade +#### 外观模式 (Facade) +>Facade 是为大规模代码 (例如类库) 提供简化接口的对象。Facade 可以: + +>1. 由于其针对常见任务有各种易用函数,可以让软件库更易于使用、理解和测试; + +>2. 在某些情况下,让库更易读; + +>3. 减少库的内部工作对外部代码的依赖,允许系统开发时有更大的灵活度; + +>4. 可以将一些低劣设计的 API 包装到一个设计良好的 API 中。 + + ![Facade](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/facade.svg "Fig. 11") +AngularJS 中已经有很多 facade。实际上,你每次为现有功能提供更高层级的 API 时,都是在创建 facade。 + +例如,让我们看看如何创建一个 `XMLHttpRequest` POST 请求: + ```JavaScript var http = new XMLHttpRequest(), @@ -649,7 +668,11 @@ http.onreadystatechange = function () { } http.send(params); ``` + +但是如果我们想要用 AngularJS 的 `$http` service 来发送数据: + ```JavaScript $http({ @@ -661,33 +684,56 @@ $http({ alert(response); }); ``` -or we can even: +我们甚至可以用: + ```JavaScript $http.post('/someUrl', data) .then(function (response) { alert(response); }); ``` + +第二选项使用了一个预先设定好的版本,用于向指定的 URL 创建一个 HTTP POST 请求。 + +`$resource` 则创建了在 `$http` service 之上更高层级的抽象化。我们会在后面 [Active Record](#active-record) 和 [代理模式 (Proxy)](#proxy) 章节中更深入的探讨此 service。 + + +#### 代理模式 (Proxy) -#### Proxy +>所谓的代理者是指一个类可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。 + ![Proxy](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/proxy.svg "Fig. 9") +我们可以将代理分为三种不同的类型: + -- Virtual Proxy -- Remote Proxy -- Protection Proxy +- 虚拟代理 (Virtual Proxy) +- 远端代理 (Remote Proxy) +- 保护代理 (Protection Proxy) +本节将讨论虚拟代理在 AngularJS 中的实现。 + +下面的代码会调用 `get` 函数,其属于一个名为 `User` 的 `$resource` 实例 : + ```JavaScript var User = $resource('/users/:id'), @@ -695,9 +741,15 @@ var User = $resource('/users/:id'), console.log(user); //{} ``` +这里的 `console.log` 将输出一个空对象。原因是当 `User.get` 被执行时,幕后所对应的 AJAX 请求是在异步运行。当 `console.log` 被调用时,我们尚未获得 user 的内容。`User.get` 在发出 GET 请求之后,会立刻返回一个空对象,并保留指向此对象的引用。我们可以将这个对象想像成一个虚拟代理 (简单的占位器)。当客户端从服务器收到响应时,再将实际数据植入此代理对象。 + +这在 AngularJS 中是如何工作的?让我们来看下面的代码: + ```JavaScript function MainCtrl($scope, $resource) { @@ -709,22 +761,42 @@ function MainCtrl($scope, $resource) { ```html ``` + +当上面的代码最初执行时,`$scope` 对象内的 `user` 属性将被赋值为一个空对象 (`{}`),这意味着 `user.name` 的值是 undefined,网页也不会渲染任何内容。一旦服务器返回请求响应,AngularJS 会将来自服务器的数据植入此对象。在下一次 `$digest` 循环中,AngularJS 将会探测到 `$scope.user` 中的变化,然后更新页面。 + -#### Active Record +#### Active Record 模式 +>Active Record 是一种包含数据和行为的对象。通常这些对象中的多数对象都是持久的。Active Record 对象负责处理与数据库的交流,以实现创建、更新、接收或者删除数据。It may delegate this responsibility to lower level objects but calls to instance or static methods of the active record object cause the database communication. + ![Active Record](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/active-record.svg "Fig. 7") +AngularJS 定义了一种名为 `$resource` 的 service。在 AngularJS 当前版本 (1.2+) 中,它是以非 AngularJS 核心的模块形式发布。 + +根据 AngularJS 文档,`$resource` 是: + +>一个用来创建资源对象的 factory,其功用是与 RESTful 服务器端数据源进行交互操作。返回收到的资源对象包含一些 action 方法以提供高层级功能,而无需使用低层级的 `$http` service。 + + +`$resource` 可以按以下方式使用: + ```JavaScript var User = $resource('/users/:id'), @@ -736,37 +808,73 @@ var User = $resource('/users/:id'), user.$save(); ``` +调用 `$resource` 将会给 model 实例创建一个构造函数。每个 model 实例都包含用于不同 CRUD 操作的方法函数。 + +如此一来,我们可以用下面的形式来使用构造函数和其静态方法: + ```JavaScript User.get({ userid: userid }); ``` +以上代码会立即返回一个空对象并保留指向该对象的引用。当响应成功返回并解析后,AngularJS 会将所收到的数据植入该对象(参见[代理模式](#proxy))。 + +更多有关 `$resource` 的内容请参阅 [The magic of $resource](http://blog.mgechev.com/2014/02/05/angularjs-resource-active-record-http/) 和 [AngularJS 文档](https://docs.angularjs.org/api/ngResource/service/$resource)。 + +由于 Martin Fowler 说过 + +>Active Record 对象负责处理与数据库的通信,以实现... + +而 `$resource` 是用于 RESTful 服务而非数据库交互,所以它并未完整的实现 Active Record 模式。但我们还是可以认为它是“类似 Active Record 的 RESTful 通信”。 + -#### Intercepting Filters +#### 截取筛选器 (Intercepting Filters) +>创建可组合的筛选器链条来完成网页请求过程中常用的预处理和后处理任务。 + ![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/intercepting-filters.svg "Fig. 3") +在很多情况下,你需要对 HTTP 请求进行各种预处理和/或后处理工作。使用截取筛选器,你可以根据所给定的 HTTP 请求/响应的头部和正文内容来预处理或者后处理它们,以加入相应的日志、安全和其它信息。截取筛选器模式包含一个筛选器链条,并按照顺序对数据进行处理。每节筛选器的输出即成为下一节的输入。 + +AngularJS 在 `$httpProvider` 中实作了截取筛选器。`$httpProvider` 拥有一个名为 `interceptors` 的数组,其中包含一组对象。每个对象都可能拥有以下属性:`request`、`response`、`requestError` 和 `responseError`。 + +`requestError` 即为一个截取器,每当之前的 `request` 截取器抛出错误或者被拒绝时就会调用 `requestError`。相应的,`responseError` 则是在之前的 `response` 截取器抛出错误时被调用。 + +以下是一个使用对象字面量 (object literal) 添加截取器的简单例子: + ```JavaScript $httpProvider.interceptors.push(function($q, dependency1, dependency2) { @@ -1185,7 +1293,7 @@ app.factory('foo', function () { Once we want to inject `foo` inside any other component we won't be able to use the private methods, but only the public ones. This solution is extremely powerful especially when one is building a reusable library. -### Data Mapper +#### Data Mapper >A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in memory data representation (the domain layer). The goal of the pattern is to keep the in memory representation and the persistent data store independent of each other and the data mapper itself. @@ -1261,7 +1369,7 @@ And the following partial: ``` -### Observer Pattern as an External Service +#### Observer Pattern as an External Service ##### About From 7534cbdb17e51e27795fec669e1cbcb2faa806d2 Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Fri, 18 Sep 2015 15:07:18 +1200 Subject: [PATCH 16/29] Finish 'Composite' section --- i18n/README-zh-cn.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 7e1800f..5cf2842 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -891,21 +891,33 @@ $httpProvider.interceptors.push(function($q, dependency1, dependency2) { ### Directives -#### Composite +#### 组合模式 (Composite) +>组合模式是一种树状结构设计模式,是将一组相似的对象与一个单一的对象实例一致对待。将对象组合成树形结构以表示“部分-整体”的树形结构。 + ![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/composite.svg "Fig. 3") +根据“四人帮”的经典论述, MVC 实际是以下部件的组合: + -- Strategy -- Composite -- Observer +- 策略模式 (Strategy) +- 组合模式 (Composite) +- 观察者模式 (Observer) +其中,页面 (view) 即是各部件的组合。非常相似的是,AngularJS 中的页面 (view) 就是由 directives 及其对应的 DOM 元素所形成的组合。 + +让我们来看以下例子: + ```HTML @@ -934,13 +946,22 @@ myModule.directive('zippy', function () { }); ``` +以上例子定义了一个简单的 directive,其功能是一个 UI 构件。此构件 (名为 "zippy") 包含头部和内容。点击其头部会切换内容部分显示或隐藏。 + +在第一段例子中,我们注意到整个 DOM 树就是由很多元素形成的组合。其根组件是 `html` 元素,紧接着是嵌套的 `head` 和 `body` 等等。 + +在第二段 JavaScript 例子中,我们看到 directive 的 `template` 属性又包含了 `ng-transclude` directive 标记。因此,在 directive `zippy` 中又存在另一个名为 `ng-transclude` 的 directive。理论上我们可以无限的嵌套这些组件直到抵达叶节点 (leaf node)。 + -### Interpreter +#### 解释器模式 (Interpreter) >In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence. From 6c2d106ecf93e8110672fa96e3866a3a3c1f8ae6 Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Tue, 22 Sep 2015 01:22:55 +1200 Subject: [PATCH 17/29] Chinese translation, Directives section is done --- i18n/README-zh-cn.md | 72 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 5cf2842..0caa95a 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -946,7 +946,7 @@ myModule.directive('zippy', function () { }); ``` -以上例子定义了一个简单的 directive,其功能是一个 UI 构件。此构件 (名为 "zippy") 包含头部和内容。点击其头部会切换内容部分显示或隐藏。 +以上例子定义了一个简单的 directive,其功能是一个 UI 构件。此构件 (名为 "zippy") 包含头部结构和正文内容。点击其头部会切换正文部分显示或隐藏。 @@ -955,27 +955,42 @@ This example defines a simple directive, which is a UI component. The defined co - -在第二段 JavaScript 例子中,我们看到 directive 的 `template` 属性又包含了 `ng-transclude` directive 标记。因此,在 directive `zippy` 中又存在另一个名为 `ng-transclude` 的 directive。理论上我们可以无限的嵌套这些组件直到抵达叶节点 (leaf node)。 +我们可以从第二段 JavaScript 例子看到,此 directive 的 `template` 属性又包含了 `ng-transclude` directive 标记。因此,在 `zippy` directive 中又存在另一个 `ng-transclude` directive。理论上我们可以无限的嵌套这些组件直到抵达叶节点 (leaf node)。 #### 解释器模式 (Interpreter) +>解释器模式是一种对计算机语言的语句进行解释估值的设计模式。其基本理念就是在该语言中,给每个终结符或者非终结符表达式赋予一个类结构。一个语句的语法树就是一个对该语句进行解释的组合模式的结构实例。 + ![Interpreter](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/interpreter.svg "Fig. 6") +在 AngularJS 的 `$parse` service 背后,其提供了一个 DSL (领域专用语言) 语言的解释器实例。此 DSL 语言是一种精简修改版的 JavaScript。JavaScript 表达式与 AngularJS 表达式之间的区别是: + + +- 可以包含 UNIX 类管道语法的过滤器 +- 不会抛出任何错误 +- 不含任何控制流语句 (异常、循环、条件语句,但可以使用三元运算符) +- 在特定的上下文环境中进行解析估值 (当前 `$scope` 的上下文) + +`$parse` service 中定义了两个主要的组件: + ```JavaScript //Responsible for converting given string into tokens @@ -984,9 +999,15 @@ var Lexer; var Parser; ``` +当给定的表达式被分词后,出于性能需求会被内部缓存。 + +AngularJS DSL 中的终结符表达式定义如下: + ```JavaScript var OPERATORS = { @@ -1019,11 +1040,20 @@ var OPERATORS = { }; ``` +我们可以将每个终结符所属函数看作是 `AbstractExpression` 接口的实现。 + +每个 `Client` 在一个特定的上下文和作用域中解释给定的 AngularJS 表达式。 + +以下是 AngularJS 表达式: + ```JavaScript // toUpperCase filter is applied to the result of the expression @@ -1031,20 +1061,35 @@ Few sample AngularJS expressions are: (foo) ? bar : baz | toUpperCase ``` -#### Template View +#### 模版视图模式 (Template View) +>模版视图指的是用嵌入标签符号的方式将信息渲染为 HTML 形式。 + ![Template View](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/template-view.svg "Fig. 8") +渲染动态页面并不是件简单的事情,包含大量字符串的连接、修改和一些难以解决的操作。创造动态页面的最简单的方法是编写自己的标记符号并在其中嵌入表达式。稍后在给定的上下文中对这些表达式进行解析,从而将整个模版编译成其最终格式,此处即为 HTML (或 DOM) 格式。这就是模版引擎的功用 - 取得给定的 DSL,在恰当的上下文中进行解析,并转换成其最终格式。 + +模版是一种常用技术,尤其是在后端应用中。例如,你可以将 PHP 代码嵌入到 HTML 中形成动态页面,也可以使用 Smarty 模版引擎,或者还可以使用 eRuby 将 Ruby 代码嵌入静态页面中。 + +适用于 JavaScript 的模版引擎有很多,例如 mustache.js、handlebars 等。大部分引擎是将模版以字符串的方式进行操作。模版可以存在于不同的地方:一是静态文件,其可以通过 AJAX 方式获取;二是以 `script` 形式嵌在视图中;或甚至是内嵌在 JavaScript 中。 + +例如: + ```html ``` +模版引擎在一个给定的上下文中编译此字符串,将其转换为 DOM 元素。如此,所有嵌在标记符中的表达式都会被解析,并替换成其计算值。 + +例如,如果以 `{ names: ['foo', 'bar', 'baz'] }` 对象为上下文对上面的模版进行解析,我们可以得到: + ```html

Names

@@ -1066,10 +1117,16 @@ For example if we evaluate the template above in the context of the following ob baz ``` +AngularJS 模版实际就是 HTML,而非类似其他传统模版的中间层格式。AngularJS 编译器会遍历 DOM 树并搜索已知的 directive (适用于元素、属性、类或甚至注释)。当 AngularJS 找到任何 directive,就会调用其所属的逻辑代码,在当前作用域的上下文中解析其中的表达式。 + +例如: + ```html
    @@ -1077,14 +1134,19 @@ For example:
``` +在下列作用域的上下文中: + ```javascript $scope.names = ['foo', 'bar', 'baz']; ``` +会生成跟上面相同的结果。其主要区别是,模版是包装在 HTML 中,而非 `script` 标签之间。 + ### Scope From 5afe9ac166baff3e9a6b0d4c9154ce76a3d6f76f Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Tue, 22 Sep 2015 17:05:03 +1200 Subject: [PATCH 18/29] Chinese translation: finished Observer section --- i18n/README-zh-cn.md | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 0caa95a..94fa70d 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -31,7 +31,7 @@ * [观察者模式 (Observer)](#observer) * [责任链模式 (Chain of Responsibilities)](#chain-of-responsibilities) * [命令模式 (Command)](#command) - * [Controller](#controller-1) + * [Controllers](#controllers-1) * [页面控制器 (Page Controller)](#page-controller) * [其它](#others) * [模块模式 (Module Pattern)](#module-pattern) @@ -48,6 +48,8 @@ - [法文翻译](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) 译者:[manekinekko](https://github.com/manekinekko) - [俄文翻译](http://habrahabr.ru/post/250149/) +(此中文版尽量将所有 AngularJS 的组件名称和专有术语保留为原有英文或将译名放置在注释内,原因是这些词汇通常会被直接用于程序源代码中,保留英文有助于避免歧义。如果您对译文有任何改进建议,请提交 Pull Request。) + ## 摘要 学习新事物的最好方式之一就是观察其如何运用整合已知的知识。本文将介绍基本的面向对象、设计模式和架构模式概念,而非向读者传授如何熟练使用这些设计或架构模式。本文的主旨是细述各种软件设计和架构模式是如何运用在 AngularJS 或者任何单页 AngularJS 应用中。 @@ -1150,15 +1152,24 @@ will produce the same result as the one above. The main difference here is that ### Scope -#### Observer +#### 观察者模式 (Observer) +>观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。 + ![Observer](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/observer.svg "Fig. 7") +AngularJS 应用中 scope 之间有两种互相通信的方式。其一是在子 scope 中调用父 scope 的函数方法,这是基于子 scope 与其父母的原型继承关系 (参见 [Scope](#scope))。这种方式允许一种的单向通信 - 从子到父。当然有些时候也需要在父 scope 的上下文中调用子 scope 的函数方法或者通知其某个触发事件。对于此需求,AngularJS 内置了一种观察者模式的实现。观察者模式的另一个使用场景是,如果多个 scope 都关注某个事件,但该事件触发所处上下文对应的 scope 与其它这些 scope 并无联系。这可以切断不同 scope 之间的耦合关系,独立的存在。 + +每个 AngularJS scope 都含有几个公有函数:`$on`、`$emit` 和 `$broadcast` 。`$on` 函数接受事件主题为第一参数,事件回调函数做为第二参数。我们可以将回调函数看作为一个观察者,即实作 `Observer` 接口的对象 (因为JavaScript 的头等函数特性,所以我们只需提供 `notify` 函数方法的实现)。 + ```JavaScript function ExampleCtrl($scope) { @@ -1168,10 +1179,16 @@ function ExampleCtrl($scope) { } ``` +在这种方式中,当前 scope 会“订阅”类别为 `event-name` 的事件。当 `event-name` 在任何父 scope 或子 scope 中被触发后,`handler` 将被调用。 + +`$emit` 和 `$broadcast` 函数则分别被用于在 scope 链中向上或向下触发事件。例如: + ```JavaScript function ExampleCtrl($scope) { @@ -1179,16 +1196,28 @@ function ExampleCtrl($scope) { } ``` +以上例子中的 scope 会向上方的 scope 触发 `event-name` 事件。意思是所有订阅了 `event-name` 事件的的父 scope 都会得到通知并执行其 `handler` 回调函数。 + +`$broadcast` 函数调用与此类似。唯一的区别是事件是向下传递给所有子 scope。每个 scope 可以给任何事件订阅配属多个回调函数 (即,一个给定事件对应多个观察者)。 + +在 JavaScript 社区中,这种模式又被称为发布/订阅模式。 + +更好的实战例子请参见[观察者模式作为外部服务](#observer-pattern-as-an-external-service)章节。 + -#### Chain of Responsibilities +#### 责任链模式 (Chain of Responsibilities) >The chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain. @@ -1517,7 +1546,7 @@ function ObserverExample(ObserverService, $timeout, $scope) { 1. [维基百科](https://en.wikipedia.org/wiki) 本文所有设计模式的简介都引自维基百科。 2. [AngularJS 文档](https://docs.angularjs.org) 3. [AngularJS 源码库](https://github.com/angular/angular.js) -4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) +4. [页面控制器 (Page Controller)](http://msdn.microsoft.com/en-us/library/ff649595.aspx) 5. [企业应用架构模式 (P of EAA)](http://martinfowler.com/books/eaa.html) 6. [Using Dependancy Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) 7. [Why would one use the Publish/Subscribe pattern (in JS/jQuery)?](https://stackoverflow.com/questions/13512949/why-would-one-use-the-publish-subscribe-pattern-in-js-jquery) From f465557ebca90410795118e060e3448603f51086 Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Sat, 26 Sep 2015 02:51:16 +1200 Subject: [PATCH 19/29] Chinese translation: finished Scope section. --- i18n/README-zh-cn.md | 49 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 94fa70d..5fb82d8 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -1219,20 +1219,38 @@ For a best practice example see [Observer Pattern as an External Service](#obser #### 责任链模式 (Chain of Responsibilities) +>责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。 + ![Chain of Responsibilities](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") +AngularJs 应用中的 scope 组成了一个等级体系,称为 scope 链。其中有些 scope 是“独立的”,指的是它们并不从其父 scope 进行原型继承,而是通过 `$parent` 属性进行连接。 + +当调用 `$emit` 或者 `$broadcast` 时,我们可以将 scope 链看作事件传递总线,或更精确的说就是责任链。每当某个事件被触发时,无论是向上还是向下传递 (基于调用不同的函数方法),链条中紧随而后的 scope 可能会进行如下操作: + +- 处理该事件并传递给链条中的下一环 +- 处理该事件并终止传递 +- 不处理此事件,而直接将事件传递给下一环 +- 不处理此事件,并终止传递 + +从下面的例子中可以看到 `ChildCtrl` 触发了一个在 scope 链条中向上传递的事件。每个父 scope (`ParentCtrl` 和 `MainCtrl` 中的 scope) 将处理此事件,即在 console 中输出记录 `"foo received"`。如果某个 scope 被认为是最终目标,则可以此处调用此事件对象 (即为回调函数所传递的参数) 的 `stopPropagation` 方法。 + ```JavaScript myModule.controller('MainCtrl', function ($scope) { @@ -1252,33 +1270,57 @@ myModule.controller('ChildCtrl', function ($scope) { }); ``` +上面这些被注入到 controller 中的 scope 即为稍早提到的 UML 图中的各个不同 handler 。 + -#### Command +#### 命令模式 (Command) +>在面向对象程式设计的范畴中,命令模式是一种行为设计模式,它尝试以物件来代表并封装所有用于在稍后某个时间调用一个函数方法所需要的信息。此信息包括所要调用的函数的名称、宿主对象及其参数值。 + ![Command](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/command.svg "Fig. 11") +在继续讨论命令模式的应用之前,让我们来看看 AngularJS 是如何实现数据绑定的。 + +当我们需要将模型与视图绑定时,可以使用 `ng-bind` (单向数据绑定) 或 `ng-model` (双向数据绑定) directive。例如,下面的代码可以将 `foo` 模型中的每个变化反映到视图中: + ```html ``` +每当我们改变 `foo` 的值时,span 中的 inner text 就会随之改变。我们可以使用更复杂的 AngularJS 表达式来实现同样的效果,例如: + ```html ``` +上面的例子中,span 的文本值等于 `foo` 和 `bar` 的值串联后再转为大写字母。让我们来看看幕后都发生了什么事? + +每个 `$scope` 都含有名为 `$watch` 的函数方法。当 AngularJS 编译器找到 `ng-bind` directive 时,就会为 `foo + ' ' + bar | uppercase` 表达式创建一个新的监视器,即 `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`。每当表达式的值改变时,其中的回调函数就会被触发。在本例中,回调将会更新 span 的文本值。 + +下面是 `$watch` 函数实现的头几行: + ```javascript $watch: function(watchExp, listener, objectEquality) { @@ -1295,11 +1337,14 @@ $watch: function(watchExp, listener, objectEquality) { //... ``` +我们可以将 `watcher` 对象视为一条命令。该命令的表达式会在每次 [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) 循环中被估值。一旦 AngularJS 发现表达式中的数值改变,就会调用 `listener` 函数。`watcher` 命令中封装了用以完成以下任务所需的全部信息。任务包括监视所给定的表达式并将命令执行委托给 `listener` 函数 (实际接收者) 。我们可以将 `$scope` 视为命令的 `Client` (客户),将 `$digest` 循环视为命令的 `Invoker` (执行者)。 + ### Controllers -#### Page Controller +#### 页面控制器 (Page Controller) >An object that handles a request for a specific page or action on a Web site. Martin Fowler From b6ec16c1bd533969a2008d20502e75dfc8276963 Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Tue, 29 Sep 2015 01:37:59 +1300 Subject: [PATCH 20/29] Finish preliminary Chinese translation --- i18n/README-zh-cn.md | 106 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 5fb82d8..40a89af 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -22,7 +22,7 @@ * [外观模式 (Facade)](#facade) * [代理模式 (Proxy)](#proxy) * [Active Record 模式](#active-record) - * [截取筛选器 (Intercepting Filters)](#intercepting-filters) + * [截取筛选器模式 (Intercepting Filters)](#intercepting-filters) * [Directives](#directives-1) * [组合模式 (Composite)](#composite) * [解释器模式 (Interpreter)](#interpreter) @@ -35,7 +35,7 @@ * [页面控制器 (Page Controller)](#page-controller) * [其它](#others) * [模块模式 (Module Pattern)](#module-pattern) - * [数据映射模式 (Data Mapper)](#data-mapper) + * [数据映射器模式 (Data Mapper)](#data-mapper) * [观察者模式作为外部服务 (Observer Pattern as an External Service)](#observer-pattern-as-an-external-service) * [参考文献](#references) @@ -48,7 +48,7 @@ - [法文翻译](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) 译者:[manekinekko](https://github.com/manekinekko) - [俄文翻译](http://habrahabr.ru/post/250149/) -(此中文版尽量将所有 AngularJS 的组件名称和专有术语保留为原有英文或将译名放置在注释内,原因是这些词汇通常会被直接用于程序源代码中,保留英文有助于避免歧义。如果您对译文有任何改进建议,请提交 Pull Request。) +(由于 AngularJS 的组件名称和专有术语通常会被直接用于程序源代码中,此中文版会尽量将此类词汇保留为原有英文,或将其译名放置在注释内,以避免歧义。如果您对译文有任何改进建议,请提交 Pull Request。) ## 摘要 @@ -849,7 +849,7 @@ Since Martin Fowler states that `$resource` does not implements exactly the Active Record pattern, since it communicates with RESTful service instead of the database. Anyway, we can consider it as "Active Record like RESTful communication". --> -#### 截取筛选器 (Intercepting Filters) +#### 截取筛选器模式 (Intercepting Filters) >创建可组合的筛选器链条来完成网页请求过程中常用的预处理和后处理任务。 @@ -1346,20 +1346,38 @@ We can think of the `watcher` object as a command. The expression of the command #### 页面控制器 (Page Controller) +>页面控制器指的是用于处理网站上某个特定页面请求或操作的对象。 Martin Fowler + ![Page Controller](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/page-controller.svg "Fig. 8") +根据参考文献[4](#references): + +>页面控制器模式接受来自页面请求的输入、依照要求调用对模型执行的操作,并确定在结果页面所使用的正确视图。将调度逻辑从视图相关代码中分离。 + +由于不同的页面之间存在大量重复操作 (例如渲染页脚、页眉,处理用户会话等),各个页面控制器在一起可以构成一个层级结构。AngularJS 中的控制器 (controller) 有着相对较有限的用途。它们并不接受用户请求,那是 `$route` 或 `$state` service 的任务,页面渲染则是 `ng-view`/`ui-view` directive 的责任。 + +与页面控制器类似,AngularJS 的 controller 负责处理用户交互操作,提供并更新模型。当模型 (model) 附着于 scope 上,它就被暴露给页面 (view) 所使用。基于用户的操作行为,页面会调用已经附着于 scope 上的相应函数方法。页面控制器与 AngularJS controller 的另一个相似点则是由它们组成的层级结构。此结构于 scope 层级相对应。由此,通用的操作可以被隔离出来置于基础 controller 中。 + +AngularJS 中的 controller 与 ASP.NET WebForms 背后的代码非常相似,它们有着几乎重合的功用。以下是控制器层级的例子: + ```HTML @@ -1390,17 +1408,29 @@ function ChildCtrl($scope, User) { } ``` +此例子描述了一个最简单的通过使用基础控制器来实现重用逻辑的例子。当然,我们不推荐在生产应用中将授权逻辑置于控制器中。不同路径 (route) 的权限可以在更高的抽象层级来判定。 + +`ChildCtrl` 负责处理点击 `"Click"` 按钮之类的操作,并将模型附着于 scope 上,使其暴露给页面使用。 + ### 其它 -#### Module Pattern +#### 模块模式 (Module Pattern) +模块模式并非‘四人帮’所提出的设计模式之一,也不是来自《企业应用架构模式》。它是一种传统的 JavaScript 模式,主要用来提供封装和私密特性。 + +通过使用模块模式,你可以基于 JavaScript 的函数作用域来实现程序结构的私密性。每个模块可以拥有零至多个私有成员,这些成员都隐藏在函数的本地作用域中。此函数会返回一个对象,用于输出给定模块的公有 API: + ```javascript var Page = (function () { @@ -1423,11 +1453,20 @@ var Page = (function () { }()); ``` +上面的例子中,我们实作了一个 IIFE (立即执行函数表达式)。当它被调用后会返回一个拥有两个函数方法 (`setTitle` 和 `getTitle`) 的对象。此对象又被赋值给 `Page` 变量。 + +在此处,使用 `Page` 对象并不能直接修改在 IIFE 本地作用域内部所定义的 `title` 变量。 + +模块模式在定义 AngularJS 中的 service 时非常有用。使用此模式可以模拟 (并实现) 私密特性: + ```javascript app.factory('foo', function () { @@ -1448,30 +1487,55 @@ app.factory('foo', function () { }); ``` +当 `foo` 被注入到任何其他组件中时,我们并不能使用其私有函数方法,而只能使用公有方法。这种解决方案在搭建可重用的库时极为有用。 + -#### Data Mapper +#### 数据映射器模式 (Data Mapper) +>数据映射器指的是在持久化数据存储 (通常是关系数据库) 与内存数据表述 (domain layer) 之间执行双向传输的数据存取层。此模式的目的是保持内存中的数据表述和持久化数据存储相互独立,以及数据映射器本身的独立性。 + ![Data Mapper](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/data-mapper.svg "Fig. 10") +根据以上表述,数据映射器是用来在持久化数据存储和内存中的数据表述之间进行双向数据传输。AngularJS 应用通常是与 API 服务器进行数据交流。此服务器是用某种服务器端语言 (Ruby、PHP、Java、JavaScript 等) 实现. + +一般来说,如果服务器端提供 RESTful API,`$resource` 可以帮助我们以 Active Record 类的方式与服务器通讯。尽管在某些应用中,从服务器返回的数据并非是最适合于在前端使用的格式。 + +例如,让我们假设某个应用中,每个用户包含: + - name - address - list of friends +并且 API 提供了一下方法: + +- `GET /user/:id` - 返回指定用户的用户名和地址 +- `GET /friends/:id` - 返回指定用户的好友列表 + +一种可能的解决方案是使用两个不同的服务,分别用于以上两个方法。另一个更好的方案是,提供一个名为 `User` 的 service,它会在请求某个用户时加载该用户的好友。 + ```javascript app.factory('User', function ($q) { @@ -1494,9 +1558,15 @@ app.factory('User', function ($q) { }); ``` +如此一来,我们就创建了一个伪数据映射器,用来使我们的 API 适应 SPA (单页应用程序) 的需求。 + +我们可以以下方式使用 `User` 服务: + ```javascript function MainCtrl($scope, User) { @@ -1507,7 +1577,10 @@ function MainCtrl($scope, User) { } ``` +以及如下模版片段: + ```html
@@ -1526,17 +1599,26 @@ And the following partial:
``` -#### Observer Pattern as an External Service +#### 观察者模式作为外部服务 (Observer Pattern as an External Service) -##### About +##### 关于 +以下是一个取自[此处](https://github.com/greglbd/angular-observer-pattern)的例子。这是一个 AngularJS factory,它实作了一个观察者模式的 service。它很好的适用于 ControllerAs 方法,如果正确使用的话,它比 `$scope.$watch` 运行更有效率,比 `$emit` 和 `$broadcast` 更明确的对应唯一的 scope 或对象。 + +**用例:**你可以使用此模式来在两个使用同一模型但互不关联的控制器之间通讯。 + -##### Controller Example +##### 控制器实例 +以下例子展示了如何添附 (attach)、通知 (notify) 以及解附 (detach) 一个事件。 + ```javascript angular.module('app.controllers') @@ -1559,7 +1641,11 @@ function ObserverExample(ObserverService, $timeout) { }, 5000); } ``` + +另一种移除事件的方式 + ```javascript angular.module('app.controllers') From 6069021f3f33b7985870d0136358fff648cc75e1 Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Wed, 30 Sep 2015 22:23:44 +1300 Subject: [PATCH 21/29] Chinese translation: proofreading --- i18n/README-zh-cn.md | 204 ++++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 101 deletions(-) diff --git a/i18n/README-zh-cn.md b/i18n/README-zh-cn.md index 40a89af..de9dfc1 100644 --- a/i18n/README-zh-cn.md +++ b/i18n/README-zh-cn.md @@ -16,27 +16,27 @@ * [Services](#services) * [AngularJS 模式](#angularjs-patterns) * [Services](#services-1) - * [单例模式 (Singleton)](#singleton) - * [工厂方法模式 (Factory Method)](#factory-method) - * [修饰模式 (Decorator)](#decorator) - * [外观模式 (Facade)](#facade) - * [代理模式 (Proxy)](#proxy) + * [单例模式](#singleton) + * [工厂方法模式](#factory-method) + * [修饰模式](#decorator) + * [外观模式](#facade) + * [代理模式](#proxy) * [Active Record 模式](#active-record) - * [截取筛选器模式 (Intercepting Filters)](#intercepting-filters) + * [截取筛选器模式](#intercepting-filters) * [Directives](#directives-1) - * [组合模式 (Composite)](#composite) - * [解释器模式 (Interpreter)](#interpreter) - * [模版视图模式 (Template View)](#template-view) + * [组合模式](#composite) + * [解释器模式](#interpreter) + * [模版视图模式](#template-view) * [Scope](#scope-1) - * [观察者模式 (Observer)](#observer) - * [责任链模式 (Chain of Responsibilities)](#chain-of-responsibilities) - * [命令模式 (Command)](#command) + * [观察者模式](#observer) + * [责任链模式](#chain-of-responsibilities) + * [命令模式](#command) * [Controllers](#controllers-1) - * [页面控制器 (Page Controller)](#page-controller) + * [页面控制器模式](#page-controller) * [其它](#others) - * [模块模式 (Module Pattern)](#module-pattern) - * [数据映射器模式 (Data Mapper)](#data-mapper) - * [观察者模式作为外部服务 (Observer Pattern as an External Service)](#observer-pattern-as-an-external-service) + * [模块模式](#module-pattern) + * [数据映射器模式](#data-mapper) + * [观察者模式作为外部服务](#observer-pattern-as-an-external-service) * [参考文献](#references) @@ -48,11 +48,11 @@ - [法文翻译](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) 译者:[manekinekko](https://github.com/manekinekko) - [俄文翻译](http://habrahabr.ru/post/250149/) -(由于 AngularJS 的组件名称和专有术语通常会被直接用于程序源代码中,此中文版会尽量将此类词汇保留为原有英文,或将其译名放置在注释内,以避免歧义。如果您对译文有任何改进建议,请提交 Pull Request。) +(由于 AngularJS 的组件名称和专有术语通常会被直接用于程序源代码中,此中文版会尽量将此类词汇保留为原有英文,或将其译名放置在注释内,以避免歧义。如果您对本译文有任何改进建议,请提交 Pull Request。) ## 摘要 -学习新事物的最好方式之一就是观察其如何运用整合已知的知识。本文将介绍基本的面向对象、设计模式和架构模式概念,而非向读者传授如何熟练使用这些设计或架构模式。本文的主旨是细述各种软件设计和架构模式是如何运用在 AngularJS 或者任何单页 AngularJS 应用中。 +学习新事物的最好方式之一就是观察其如何运用整合已知的知识。本文将介绍面向对象、设计模式和架构模式的基本概念,而非向读者传授如何熟练使用这些设计或架构模式。本文的主旨是介绍 AngularJS 框架中的各种软件设计和架构模式以及如何在 AngularJS 单页应用中运用它们。 -文章最后会提及在 AngularJS 单页应用中常用的架构模式。 +在文章最后还会提及一些 AngularJS 单页应用中常用的架构模式。 @@ -86,30 +86,30 @@ Since most of the CRUD applications has common characteristics and requirements, - 可测试性 (testability) - 抽象化 (abstraction) -实现关注点分离是将 AngularJS 应用划分为相互隔离的构件,如: +关注点分离是依靠将 AngularJS 应用划分为相互隔离的构件来实现的,如: -- partials -- controllers -- directives -- services -- filters +- partials (片段) +- controllers (控制器) +- directives (指示器) +- services (服务) +- filters (筛选器) -这些构件可以在不同的模块中组合,以帮助实现更高层级的抽象化以及处理复杂事件。每个单独构件会封装应用程序中特定部分的逻辑。 +这些构件可以在不同的模块中组合,以帮助实现更高层级的抽象化以及处理复杂事件。每个单独构件都会封装应用程序中特定部分的逻辑。 ### Partials -Partial (模版片段) 实际就是 HTML 字符串,其元素和属性中可能含有一些 AngularJS 表达式。与 mustache.js 和 handlebars 等框架相比,AngularJS 的一个不同之处就是其模版并非是一种需要转化为 HTML 的中间格式。 +Partial (模版片段) 实际就是 HTML 字符串,其元素和属性中可能包含一些 AngularJS 表达式。与 mustache.js 和 handlebars 等框架相比,AngularJS 的一个不同之处就是其模版并非是一种需要转化为 HTML 的中间格式。 -每个单页应用都会在初始化时读取 `index.html` 文件。在 AngularJS 中,此文件包含有一套用来配置和启动应用程序的标准和自定义 HTML 属性、元素和注释。接下来每个用户操作将仅仅加载一个 partial 文件或者通过软件框架的数据绑定等方式改变应用的当前状态。 +每个单页应用都会在初始化时读取 `index.html` 文件。在 AngularJS 中,此文件包含有一套用来配置和启动应用程序的标准的和自定义的 HTML 属性、元素和注释。接下来的每个用户操作将仅仅加载一个 partial 文件或者通过软件框架的数据绑定等方式来改变应用的当前状态。 @@ -130,14 +130,14 @@ Initially each SPA loads `index.html` file. In the case of AngularJS this file c ``` -Partial 文件可以通过 AngularJS 表达式来定义不同用户交互操作所对应的行为。例如在上面的示例中,`ng-click` 属性的值表示执行当前 *scope* 中的 `changeFoo` 函数。 +Partial 文件可以通过 AngularJS 表达式来定义不同用户交互操作所对应的行为。例如在上面的例子中,`ng-click` 属性的值表示将执行当前 *scope* 中的 `changeFoo` 函数。 ### Controllers -AnuglarJS 中的 controller (控制器) 则是 JavaScript 函数,可以通过将函数绑定到对应的 *scope* 上, 来帮助处理用户与网页应用的交互操作 (例如鼠标或键盘事件等)。Controller 所需要的所有外部构件都是通过 AngularJS 的依赖注入 (Dependency Injection) 机制实现。Controller 还将数据绑定到 *scope* 上,从而给 partial 提供 *model* 功能。我们可以将这些数据看成是 *view model*。 +AnuglarJS 中的 controller (控制器) 本质上就是 JavaScript 函数。它可以通过将函数绑定到对应的 *scope* 上来帮助处理用户与网页应用的交互操作 (例如鼠标或键盘事件等)。Controller 所需要的所有外部构件都是通过 AngularJS 的依赖注入 (Dependency Injection) 机制实现。Controller 还会将数据也绑定到 *scope* 上,从而给 partial 提供模型 (*model*) 功能。我们可以将这些数据看成是视图模型 (*view model*)。 @@ -154,12 +154,12 @@ function MyController($scope) { } ``` -如果将以上 controller 示例与前一节中的 partial 示例结合在一起,用户就可以跟应用程序进行一些不同的交互操作。 +如果将以上 controller 示例与前一节中的 partial 示例结合在一起,用户就可以在应用程序中进行一些不同的交互操作。 -1. 通过输入框改变 `foo` 的值。由于这里使用了双向数据绑定,`foo` 值会立刻改变。 +1. 通过改写输入框中的文本来改变 `foo` 的值。由于这里使用了双向数据绑定,`foo` 值会立刻改变。 2. 点击 `Click me to change foo!` 按钮来改变 `foo` 的值。 -AngularJS 应用中的 scope 还有另一个重要的特质,即它们都被连接到一条原型链 (prototypical chain) 上 (除了那些被表明为*独立 (isolated)* 的 scope)。在这种方式中,任何子 scope 都能调用属于其父母的函数,因为这些函数是该 scope 的直接或间接原型 (prototype) 的属性。 +AngularJS 应用中的 scope 还有另一个重要的特质,即它们都被连接到一条原型链 (prototypical chain) 上 (除了那些被表明为独立 (*isolated*) 的 scope)。在这种方式中,任何子 scope 都能调用属于其父母的函数,因为这些函数是该 scope 的直接或间接原型的属性。 -以下示例展示了 scope 继承关系: +以下例子展示了 scope 继承关系: @@ -212,7 +212,7 @@ function ChildCtrl($scope) { } ``` -尽管 `div#child` 归属于 `ChildCtrl`,但由于 `ChildCtrl` 中所注入的 scope 通过原型继承了其父 scope (即 `BaseCtrl` 中注入的 scope),因此`button#parent-method` 可以接触到 `foo` 函数。 +尽管 `div#child` 归属于 `ChildCtrl`,但由于 `ChildCtrl` 中所注入的 scope 通过原型继承了其父 scope (即 `BaseCtrl` 中所注入的 scope),因此 `button#parent-method` 就可以接触到 `foo` 函数。 @@ -268,19 +268,19 @@ myModule.directive('alertButton', function () { In the example above the tag `` will be replaced button element. When the user clicks on the button the string `42` will be alerted. --> -由于本文的关注点并非解释 AnuglarJS 的完整 API,directive 就解释到这里为止。 +由于本文的关注点并非分析 AnuglarJS 的完整 API,directive 就解释到这里为止。 ### Filters -AngularJS 中的 filter (过滤器) 负责封装进行数据格式化所需的逻辑。Filter 通常被用在 partial 中,但也可以通过依赖注入方式在 controller、directive、*service* 和其它 filter 中使用。 +AngularJS 中的 filter (筛选器) 负责封装数据格式化所需的逻辑。Filter 通常被用在 partial 中,但也可以通过依赖注入方式在 controller、directive、service 以及其它 filter 中使用。 -以下是一个范例 filter 的定义,用来将给定的字符串转变为大写。 +以下定义了一个 filter 范例,用来将给定的字符串转变为大写。 @@ -293,7 +293,7 @@ myModule.filter('uppercase', function () { }); ``` -此 filter 可以用 Unix 的管道语法用于 partial 中。 +此 filter 可以通过 Unix 管道语法在 partial 中使用。 @@ -315,7 +315,7 @@ function MyCtrl(uppercaseFilter) { ### Services -所有其它逻辑,如果不属于以上所述构件,则应该放置到 service (服务) 中。Service 通常会封装领域专用逻辑 (domain specific logic)、持久层逻辑 (persistence logic)、XHR、WebSockets 等。当应用程序中 controller 变得过于臃肿是,就应该考虑将重复的代码放入一个 service 中。 +所有其它逻辑,如果不属于以上所述构件,则应该放置到 service (服务) 中。Service 通常会封装领域专用逻辑 (domain specific logic)、持久层逻辑 (persistence logic)、XHR、WebSockets 等。当应用程序中 controller 变得过于臃肿时,就应该考虑将重复的代码放入一个 service 中。 @@ -357,7 +357,7 @@ In the last chapter we are going to take a look at some architectural patterns, #### 单例模式 (Singleton) ->单例模式是一种软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。如果系统在仅有一个对象或者有限个数的对象实例的环境中运行更加高效,也时常被归属为单例模式概念。 +>单例模式是一种软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。如果某个系统在仅有单个对象或者有限个数的对象实例的环境中运行更加高效,也时常被归属为单例模式概念。 @@ -374,10 +374,10 @@ AngularJS 会按照以下算法来解决构件所需要的依赖关系: When given dependency is required by any component, AngularJS resolves it using the following algorithm: --> -- 提取所依赖组件的名称,并查询哈希表 (该表是定义在一个闭包中,所以外部不可见)。 +- 提取所依赖组件的名称并查询哈希表 (该表是定义在一个闭包中,所以外部不可见)。 - 如果此依赖组件已经存在于应用中,AngularJS 会在需要的时候以参数的形式将其传递给对应的构件。 - 如果所依赖的组件不存在: - - AngularJS 首先会调用其提供者的生成函数,如 `$get`。值得注意的是,创建此依赖组件的实例时,可能会对算法内容进行递归调用,以解决该组件本事的依赖关系。 + - AngularJS 首先会调用其提供者的生成函数,如 `$get`。值得注意的是,创建此依赖组件的实例时,可能会对算法内容进行递归调用,以解决该组件本身的依赖关系。 - AngularJS 然后会将其缓存在上面提到的哈希表中。 - AngularJS 最后会在需要的时候将其传递给对应组件。 @@ -419,12 +419,12 @@ function getService(serviceName) { } ``` -由于每个 service 只会被实例化一次,我们可以将每个 service 看成是一个单例。缓存则可以被认为是单例管理器。这里与上面展示的 UML 图有微小的区别,那就是我们并不将单例的静态私有的 reference 保存在其构造 (constructor) 函数中,而是将 reference 保存在单例管理器中 (以上代码中的 `cache`)。 +由于每个 service 只会被实例化一次,我们可以将每个 service 看成是一个单例。缓存则可以被认为是单例管理器。这里与上面展示的 UML 图有微小的区别,那就是我们并不将单例的静态私有引用保存在其构造函数中,而是将引用保存在单例管理器中 (以上代码中的 `cache`)。 -如此 service 实际还是单例,但并不是以传统单例设计模式的方法所实现。相比之下,这种方式有如下优点: +如此一来,service 实际还是单例,但并不是以传统单例设计模式的方法所实现。相比之下,这种方式有如下优点: @@ -444,7 +444,7 @@ For further discussion on this topic Misko Hevery's [article](http://googletesti #### 工厂方法模式 (Factory Method) ->工厂方法模式是一种创建型模式,其实现了一个“工厂方法”概念来处理在不指定对象具体类型的情况下创建对象的问题。其解决方式不是通过构造函数来完成,而是在接口 (abstract class) 中定义一个用来创建对象的工厂方法,然后在实现类 (concrete classes) 中实现它。或者在一个基础类 (base class) 中实现它,而该类又可以通过继承关系被派生类 (derived class) 所重写。 +>工厂方法模式是一种创建型模式,其实现了一个「工厂方法」概念来处理在不指定对象具体类型的情况下创建对象的问题。其解决方式不是通过构造函数来完成,而是在抽象类 (abstract class) 中定义一个用来创建对象的工厂方法,然后在实体类 (concrete classes) 中实现它。或者在一个基础类 (base class) 中实现它,而该类又可以通过继承关系被派生类 (derived class) 所重写。 @@ -474,7 +474,7 @@ myModule.config(function ($provide) { ``` -在上面的代码中,我们使用 `config` 回调来定义一个新的“provider”。Provider 是一个对象,其中含有一个 `$get` 函数。由于 JavaScript 语言没有接口 (interface),而语言本身是鸭子类型 (duck-typed),所以这里提供了一个给 provider 命名工厂方法的规则。 +在上面的代码中,我们使用 `config` 回调来定义一个新的「provider」。Provider 是一个对象,其中包含一个 `$get` 函数。由于 JavaScript 语言没有接口 (interface),而语言本身是鸭子类型 (duck-typed),所以这里提供了一个给 provider 的工厂方法进行命名的规则。 @@ -540,17 +540,17 @@ From the example above we can notice how the `$get` method is actually used: instanceInjector.invoke(provider.$get, provider, undefined, servicename) ``` -上面这个代码片段调用了 `instanceInjector` 的 `invoke` 函数,其中第一个参数就是某 service 的工厂方法 (即 `$get`)。在 `invoke` 内部,`annotate` 函数又将该工厂方法作为其第一个参数。如代码所示,`annotate` 使用 AngularJS 的依赖注入机制来解决所有依赖关系。当所有依赖关系都满足后,工厂方法函数就会被调用:`fn.apply(self, args)`。 +上面这个代码片段调用了 `instanceInjector` 的 `invoke` 函数,其中第一个参数就是某 service 的工厂方法 (即 `$get`)。在 `invoke` 内部,`annotate` 函数又将该工厂方法作为其第一个参数。如代码所示,`annotate` 会使用 AngularJS 的依赖注入机制来解决所有依赖关系。当所有依赖关系都满足后,工厂方法函数就会被调用:`fn.apply(self, args)`。 -如果对照上面的 UML 图来看,我们可以认为这里的 provider 就是图中的“ConcreteCreator”,而实际组件就是“Product”。 +如果对照上面的 UML 图来看,我们可以认为这里的 provider 就是图中的「ConcreteCreator」,而实际组件就是「Product」。 -由于工厂方法能间接的创建对象,使用这种设计模式能带来一些益处。尤其是框架能够在组件实例化的过程中关注解决一些 boilerplate: +由于工厂方法能间接的创建对象,使用这种设计模式能带来很多益处。尤其是框架能够在组件实例化的过程中关注解决一些样板化问题: @@ -566,6 +566,7 @@ There are a few benefits of using the factory method pattern in this case, becau --> #### 修饰模式 (Decorator) + >修饰模式又被称为 wrapper,与适配器模式的别名一样。它是一种可以动态或静态的往一个独立对象中添加新行为,而不影响同一个类所生成的其它对象的行为的设计模式。 @@ -606,7 +607,7 @@ myModule.config(function ($provide) { }); ``` -上述例子定义了一个名为 `foo` 的新 service。在 `config` 中,回调了 `$provide.decorator` 函数,其第一个参数 `“foo”` 就是我们想要修饰的 service 的名称,而第二个参数则是实现修饰内容的函数。`$delegate` 则保持引用原有 `foo` service。通过使用 AngularJS 的依赖注入机制,这个本地依赖的引用 (reference) 是以构造函数的第一个参数传递。在这里,我们对 service 的修饰是重写其 `bar` 函数。实际修饰内容只是多执行一条 `console.log` 语句 - `console.log('Decorated');`,然后继续在对应的上下文中调用原有 `bar` 函数。 +上述例子定义了一个名为 `foo` 的新 service。在 `config` 中,回调了 `$provide.decorator` 函数,其第一个参数 `「foo」` 就是我们想要修饰的 service 的名称,而第二个参数则是实现修饰内容的函数。`$delegate` 则保持引用原有 `foo` service。通过使用 AngularJS 的依赖注入机制,这个本地依赖的引用 (reference) 是以构造函数的第一个参数传递。在这里,我们对 service 的修饰是重写其 `bar` 函数。实际修饰内容只是多执行一条 `console.log` 语句 - `console.log('Decorated');`,然后继续在对应的上下文中调用原有 `bar` 函数。 @@ -698,19 +699,19 @@ $http.post('/someUrl', data) }); ``` -第二选项使用了一个预先设定好的版本,用于向指定的 URL 创建一个 HTTP POST 请求。 +上面第二种方式使用了一个预先设定好的版本,用于向指定的 URL 创建并发送一个 HTTP POST 请求。 -`$resource` 则创建了在 `$http` service 之上更高层级的抽象化。我们会在后面 [Active Record](#active-record) 和 [代理模式 (Proxy)](#proxy) 章节中更深入的探讨此 service。 +`$resource` 则在 `$http` service 之上建立了更高层级的抽象化。我们会在后面的 [Active Record 模式](#active-record)和[代理模式](#proxy)章节中对其进行更深入的探讨。 #### 代理模式 (Proxy) ->所谓的代理者是指一个类可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。 +>所谓的代理者,在一般意义上,是指一个类可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。 @@ -764,14 +765,14 @@ function MainCtrl($scope, $resource) { ``` -当上面的代码最初执行时,`$scope` 对象内的 `user` 属性将被赋值为一个空对象 (`{}`),这意味着 `user.name` 的值是 undefined,网页也不会渲染任何内容。一旦服务器返回请求响应,AngularJS 会将来自服务器的数据植入此对象。在下一次 `$digest` 循环中,AngularJS 将会探测到 `$scope.user` 中的变化,然后更新页面。 +当上面的代码最初执行时,`$scope` 对象内的 `user` 属性将被赋值为一个空对象 (`{}`),这意味着 `user.name` 的值是 undefined,网页也不会渲染任何内容。AngularJS 内部会保留一份对此对象的引用。一旦服务器返回对 get 请求的响应,AngularJS 会将来自服务器的数据植入此对象。在下一次 `$digest` 循环中,AngularJS 将会探测到 `$scope.user` 中的变化,然后更新页面。 #### Active Record 模式 ->Active Record 是一种包含数据和行为的对象。通常这些对象中的多数对象都是持久的。Active Record 对象负责处理与数据库的交流,以实现创建、更新、接收或者删除数据。It may delegate this responsibility to lower level objects but calls to instance or static methods of the active record object cause the database communication. +>Active Record 是一种包含数据和行为的对象。通常这些对象中的大部分数据都是持久的。Active Record 对象负责处理与数据库的交流,以实现创建、更新、接收或者删除数据。它也可能将这些任务交给更低层级的对象去完成,但数据库交流依然是通过调用 active record 对象实例或静态方法来发起。 @@ -788,7 +789,7 @@ AngularJS defines a service called `$resource`. In the current version of Angula According to the AngularJS' documentation `$resource` is: --> ->一个用来创建资源对象的 factory,其功用是与 RESTful 服务器端数据源进行交互操作。返回收到的资源对象包含一些 action 方法以提供高层级功能,而无需使用低层级的 `$http` service。 +>一个用来创建资源对象的 factory,其功用是与 RESTful 服务器端数据源进行交互操作。其返回的资源对象中包含一些提供高层级功能的 action 方法,而无需使用低层级的 `$http` 服务。 @@ -824,7 +825,7 @@ This way we can use the constructor function and its static methods by: User.get({ userid: userid }); ``` -以上代码会立即返回一个空对象并保留指向该对象的引用。当响应成功返回并解析后,AngularJS 会将所收到的数据植入该对象(参见[代理模式](#proxy))。 +以上代码会立即返回一个空对象并保留指向该对象的引用。当响应成功返回并解析后,AngularJS 会将所收到的数据植入该对象 (参见[代理模式](#proxy))。 @@ -844,7 +845,7 @@ Since Martin Fowler states that > responsibility of the Active Record object is to take care of the communication with the databse in order to create... --> -而 `$resource` 是用于 RESTful 服务而非数据库交互,所以它并未完整的实现 Active Record 模式。但我们还是可以认为它是“类似 Active Record 的 RESTful 通信”。 +而 `$resource` 是用于 RESTful 服务而非数据库交互,所以它并未完整的实现 Active Record 模式。但我们还是可以认为它是「类似 Active Record 的 RESTful 通信」。 @@ -858,7 +859,7 @@ Since Martin Fowler states that ![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/intercepting-filters.svg "Fig. 3") -在很多情况下,你需要对 HTTP 请求进行各种预处理和/或后处理工作。使用截取筛选器,你可以根据所给定的 HTTP 请求/响应的头部和正文内容来预处理或者后处理它们,以加入相应的日志、安全和其它信息。截取筛选器模式包含一个筛选器链条,并按照顺序对数据进行处理。每节筛选器的输出即成为下一节的输入。 +在很多情况下,你需要对 HTTP 请求进行各种预处理和/或后处理工作。使用截取筛选器,你可以根据所给定的 HTTP 请求/响应的头部和正文内容来预处理或者后处理它们,以加入相应的日志、安全和其它信息。截取筛选器模式包含一个筛选器链条,并按照给定的顺序对数据进行处理。每节筛选器的输出即成为下一节的输入。 @@ -895,14 +896,14 @@ $httpProvider.interceptors.push(function($q, dependency1, dependency2) { #### 组合模式 (Composite) ->组合模式是一种树状结构设计模式,是将一组相似的对象与一个单一的对象实例一致对待。将对象组合成树形结构以表示“部分-整体”的树形结构。 +>组合模式是一种树状结构设计模式,是将一组相似的对象与一个单一的对象实例一致对待。此模式的意图是将对象组合成树形结构以表现为「部分-整体」的树形结构。 ![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/composite.svg "Fig. 3") -根据“四人帮”的经典论述, MVC 实际是以下部件的组合: +根据「四人帮」的经典论述, MVC 实际是以下部件的组合: @@ -911,7 +912,7 @@ According to the Gang of Four, MVC is nothing more than combination of: - 组合模式 (Composite) - 观察者模式 (Observer) -其中,页面 (view) 即是各部件的组合。非常相似的是,AngularJS 中的页面 (view) 就是由 directives 及其对应的 DOM 元素所形成的组合。 +其中,页面即是各部件的组合。非常相似的是,AngularJS 中的页面就是由 directive 及其对应的 DOM 元素所形成的组合。 @@ -957,6 +958,7 @@ This example defines a simple directive, which is a UI component. The defined co + 我们可以从第二段 JavaScript 例子看到,此 directive 的 `template` 属性又包含了 `ng-transclude` directive 标记。因此,在 `zippy` directive 中又存在另一个 `ng-transclude` directive。理论上我们可以无限的嵌套这些组件直到抵达叶节点 (leaf node)。 -- 可以包含 UNIX 类管道语法的过滤器 +- 可以包含 UNIX 类管道语法的筛选器 - 不会抛出任何错误 - 不含任何控制流语句 (异常、循环、条件语句,但可以使用三元运算符) - 在特定的上下文环境中进行解析估值 (当前 `$scope` 的上下文) @@ -1052,7 +1054,7 @@ We can think of the function associated with each terminal as implementation of Each `Client` interprets given AngularJS expression in a specific context - specific scope. --> -以下是 AngularJS 表达式: +以下是一个 AngularJS 表达式的例子: @@ -1065,14 +1067,14 @@ Few sample AngularJS expressions are: #### 模版视图模式 (Template View) ->模版视图指的是用嵌入标签符号的方式将信息渲染为 HTML 形式。 +>模版视图指的是在 HTML 页面中嵌入标签符号以将信息渲染为 HTML 形式。 ![Template View](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/template-view.svg "Fig. 8") -渲染动态页面并不是件简单的事情,包含大量字符串的连接、修改和一些难以解决的操作。创造动态页面的最简单的方法是编写自己的标记符号并在其中嵌入表达式。稍后在给定的上下文中对这些表达式进行解析,从而将整个模版编译成其最终格式,此处即为 HTML (或 DOM) 格式。这就是模版引擎的功用 - 取得给定的 DSL,在恰当的上下文中进行解析,并转换成其最终格式。 +渲染动态页面并不是件简单的事情,其中包含大量字符串的连接、修改和一些难以解决的操作。创造动态页面的最简单的方法是编写自己的标记符号并在其中嵌入表达式。稍后在给定的上下文中对这些表达式进行解析,从而将整个模版编译成其最终格式,此处即为 HTML (或 DOM) 格式。这就是模版引擎的功用 - 提取指定的 DSL,在恰当的上下文中进行解析,并转换成其最终格式。 @@ -1083,7 +1085,7 @@ Templates are very commonly used especially in the back-end. For example, you can embed PHP code inside HTML and create a dynamic page, you can use Smarty or you can use eRuby with Ruby in order to embed Ruby code inside your static pages. --> -适用于 JavaScript 的模版引擎有很多,例如 mustache.js、handlebars 等。大部分引擎是将模版以字符串的方式进行操作。模版可以存在于不同的地方:一是静态文件,其可以通过 AJAX 方式获取;二是以 `script` 形式嵌在视图中;或甚至是内嵌在 JavaScript 中。 +适用于 JavaScript 的模版引擎有很多,例如 mustache.js、handlebars 等。大部分引擎是将模版以字符串的方式进行操作。模版可以存放在不同的位置:一是静态文件,其可以通过 AJAX 方式获取;二是以 `script` 形式嵌在视图中;或甚至是内嵌在 JavaScript 中。 @@ -1102,7 +1104,7 @@ For example: ``` -模版引擎在一个给定的上下文中编译此字符串,将其转换为 DOM 元素。如此,所有嵌在标记符中的表达式都会被解析,并替换成其计算值。 +模版引擎在一个给定的上下文中编译此字符串,将其转换为 DOM 元素。如此一来,所有嵌在标记符中的表达式都会被解析,并替换成其计算值。 @@ -1119,7 +1121,7 @@ For example if we evaluate the template above in the context of the following ob baz ``` -AngularJS 模版实际就是 HTML,而非类似其他传统模版的中间层格式。AngularJS 编译器会遍历 DOM 树并搜索已知的 directive (适用于元素、属性、类或甚至注释)。当 AngularJS 找到任何 directive,就会调用其所属的逻辑代码,在当前作用域的上下文中解析其中的表达式。 +AngularJS 模版实际就是 HTML,而非其他传统模版所使用的中间层格式。AngularJS 编译器会遍历 DOM 树并搜索已知的 directive (适用于元素、属性、类或甚至注释)。当 AngularJS 找到任何 directive,就会调用其所属的逻辑代码,在当前作用域的上下文中解析其中的表达式。 @@ -1179,7 +1181,7 @@ function ExampleCtrl($scope) { } ``` -在这种方式中,当前 scope 会“订阅”类别为 `event-name` 的事件。当 `event-name` 在任何父 scope 或子 scope 中被触发后,`handler` 将被调用。 +在这种方式中,当前 scope 会「订阅」类别为 `event-name` 的事件。当 `event-name` 在任何父 scope 或子 scope 中被触发后,`handler` 将被调用。 @@ -1207,7 +1209,7 @@ Analogical is the case when the method `$broadcast` is called. The only differen Each scope can subscribe to any event with multiple callbacks (i.e. it can associate multiple observers to given event). --> -在 JavaScript 社区中,这种模式又被称为发布/订阅模式。 +在 JavaScript 社群中,这种模式又被称为发布/订阅模式。 @@ -1226,7 +1228,7 @@ For a best practice example see [Observer Pattern as an External Service](#obser ![Chain of Responsibilities](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") -AngularJs 应用中的 scope 组成了一个层级结构,称为 scope 链。其中有些 scope 是“独立的”,指的是它们并不从其父 scope 进行原型继承,而是通过 `$parent` 属性进行连接。 +AngularJs 应用中的 scope 组成了一个层级结构,称为 scope 链。其中有些 scope 是「独立的」,指的是它们并不从其父 scope 进行原型继承,而是通过 `$parent` 属性进行连接。 @@ -1247,7 +1249,7 @@ When `$emit` or `$broadcast` are called we can think of the scope chain as event - Stop the event propagation without handling it --> -从下面的例子中可以看到 `ChildCtrl` 触发了一个在 scope 链条中向上传递的事件。每个父 scope (`ParentCtrl` 和 `MainCtrl` 中的 scope) 将处理此事件,即在 console 中输出记录 `"foo received"`。如果某个 scope 被认为是最终目标,则可以此处调用此事件对象 (即为回调函数所传递的参数) 的 `stopPropagation` 方法。 +从下面的例子中可以看到 `ChildCtrl` 触发了一个在 scope 链条中向上传递的事件。每个父 scope (`ParentCtrl` 和 `MainCtrl` 中的 scope) 将处理此事件,即在 console 中输出记录 `"foo received"`。如果某个 scope 被认为是最终目标,则可以在此处调用事件对象 (即回调函数中所传递的参数) 的 `stopPropagation` 方法。 @@ -1270,7 +1272,7 @@ myModule.controller('ChildCtrl', function ($scope) { }); ``` -上面这些被注入到 controller 中的 scope 即为稍早提到的 UML 图中的各个不同 handler 。 +上面这些被注入到 controller 中的 scope 即为稍早提到的 UML 图中的各个 handler 。 @@ -1298,7 +1300,7 @@ When we want to bind our model to the view we use the directives `ng-bind` (for ``` -每当我们改变 `foo` 的值时,span 中的 inner text 就会随之改变。我们可以使用更复杂的 AngularJS 表达式来实现同样的效果,例如: +每当我们改变 `foo` 的值时,span 中的 inner text 就会随之改变。我们可以使用 AngularJS 表达式来实现更复杂的的同类效果,例如: @@ -1307,17 +1309,17 @@ Now each time we change the value of `foo` the inner text of the span will be ch ``` -上面的例子中,span 的文本值等于 `foo` 和 `bar` 的值串联后再转为大写字母。让我们来看看幕后都发生了什么事? +上面的例子中,span 内的文本值等于 `foo` 和 `bar` 的值串联后再转为大写字母。让我们来看看幕后都发生了什么事? -每个 `$scope` 都含有名为 `$watch` 的函数方法。当 AngularJS 编译器找到 `ng-bind` directive 时,就会为 `foo + ' ' + bar | uppercase` 表达式创建一个新的监视器,即 `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`。每当表达式的值改变时,其中的回调函数就会被触发。在本例中,回调将会更新 span 的文本值。 +每个 `$scope` 都含有名为 `$watch` 的函数方法。当 AngularJS 编译器找到 `ng-bind` directive 时,就会为 `foo + ' ' + bar | uppercase` 表达式创建一个新的监视器,即 `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`。每当表达式的值改变时,监视器中的回调函数就会被触发。在本例中,回调将会更新 span 的文本。 -下面是 `$watch` 函数实现的头几行: +下面是 `$watch` 函数的头几行: @@ -1337,14 +1339,14 @@ $watch: function(watchExp, listener, objectEquality) { //... ``` -我们可以将 `watcher` 对象视为一条命令。该命令的表达式会在每次 [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) 循环中被估值。一旦 AngularJS 发现表达式中的数值改变,就会调用 `listener` 函数。`watcher` 命令中封装了用以完成以下任务所需的全部信息。任务包括监视所给定的表达式并将命令执行委托给 `listener` 函数 (实际接收者) 。我们可以将 `$scope` 视为命令的 `Client` (客户),将 `$digest` 循环视为命令的 `Invoker` (执行者)。 +我们可以将 `watcher` 对象视为一条命令。该命令的表达式会在每次 [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) 循环中被估值。一旦 AngularJS 发现表达式中的数值改变,就会调用 `listener` 函数。`watcher` 命令中封装了用以完成以下任务所需的全部信息。任务包括监视所给定的表达式和将命令执行委托给 `listener` 函数 (实际接收者) 。我们可以将 `$scope` 视为命令的 `Client` (客户),将 `$digest` 循环视为命令的 `Invoker` (执行者)。 ### Controllers -#### 页面控制器 (Page Controller) +#### 页面控制器模式 (Page Controller) >页面控制器指的是用于处理网站上某个特定页面请求或操作的对象。 Martin Fowler ->页面控制器模式接受来自页面请求的输入、依照要求调用对模型执行的操作,并确定在结果页面所使用的正确视图。将调度逻辑从视图相关代码中分离。 +>页面控制器模式接受来自页面请求的输入、对具体模型调用所要求的操作,并决定在结果页面所应该使用的正确视图。将调度逻辑从视图相关代码中分离。 -由于不同的页面之间存在大量重复操作 (例如渲染页脚、页眉,处理用户会话等),各个页面控制器在一起可以构成一个层级结构。AngularJS 中的控制器 (controller) 有着相对较有限的用途。它们并不接受用户请求,那是 `$route` 或 `$state` service 的任务,页面渲染则是 `ng-view`/`ui-view` directive 的责任。 +由于不同的页面之间存在大量重复操作 (例如渲染页脚、页眉,处理用户会话等),各个页面控制器在一起可以构成一个层级结构。AngularJS 中的控制器 (controller) 有着相对较有限的用途。它们并不接受用户请求,那是 `$route` 或 `$state` service 的任务,页面渲染则是 `ng-view` 或 `ui-view` directive 的责任。 -与页面控制器类似,AngularJS 的 controller 负责处理用户交互操作,提供并更新模型。当模型 (model) 附着于 scope 上,它就被暴露给页面 (view) 所使用。基于用户的操作行为,页面会调用已经附着于 scope 上的相应函数方法。页面控制器与 AngularJS controller 的另一个相似点则是由它们组成的层级结构。此结构于 scope 层级相对应。由此,通用的操作可以被隔离出来置于基础 controller 中。 +与页面控制器类似,AngularJS 的 controller 负责处理用户交互操作,提供并更新模型。当模型 (model) 附着于 scope 上,它就被暴露给页面 (view) 所使用。基于用户的操作行为,页面会调用已经附着于 scope 上的相应函数方法。页面控制器与 AngularJS controller 的另一个相似点则是由它们所组成的层级结构。此结构于 scope 层级相对应。由此,共通的操作可以被隔离出来置于基础 controller 中。 -AngularJS 中的 controller 与 ASP.NET WebForms 背后的代码非常相似,它们有着几乎重合的功用。以下是控制器层级的例子: +AngularJS 中的 controller 与 ASP.NET WebForms 背后的代码非常相似,它们有着几乎相同的功用。以下是 controller 层级结构例子: @@ -1494,7 +1496,7 @@ Once we want to inject `foo` inside any other component we won't be able to use #### 数据映射器模式 (Data Mapper) ->数据映射器指的是在持久化数据存储 (通常是关系数据库) 与内存数据表述 (domain layer) 之间执行双向传输的数据存取层。此模式的目的是保持内存中的数据表述和持久化数据存储相互独立,以及数据映射器本身的独立性。 +>数据映射器指的是在持久化数据存储 (通常是关系型数据库) 与内存数据表述 (domain layer) 之间执行双向传输的数据存取层。此模式的目的是保持内存中的数据表述和持久化数据存储相互独立,以及数据映射器本身的独立性。 @@ -1520,7 +1522,7 @@ For instance, lets assume we have application in which each user has: - address - list of friends -并且 API 提供了一下方法: +并且 API 提供了以下方法: @@ -1532,7 +1534,7 @@ And our API has the methods: - `GET /friends/:id` - returns the list of friends of given user --> -一种可能的解决方案是使用两个不同的服务,分别用于以上两个方法。另一个更好的方案是,提供一个名为 `User` 的 service,它会在请求某个用户时加载该用户的好友。 +一种可能的解决方案是使用两个不同的服务,分别用于以上两个方法。另一个更好的方案是,提供一个名为 `User` 的 service,它会在请求某个用户时同时加载该用户的好友。 @@ -1563,7 +1565,7 @@ app.factory('User', function ($q) { This way we create pseudo-data mapper, which adapts our API according to the SPA requirements. --> -我们可以以下方式使用 `User` 服务: +我们可以通过下方式使用 `User` 服务: @@ -1603,12 +1605,12 @@ And the following partial: ##### 关于 -以下是一个取自[此处](https://github.com/greglbd/angular-observer-pattern)的例子。这是一个 AngularJS factory,它实作了一个观察者模式的 service。它很好的适用于 ControllerAs 方法,如果正确使用的话,它比 `$scope.$watch` 运行更有效率,比 `$emit` 和 `$broadcast` 更明确的对应唯一的 scope 或对象。 +以下是一个取自[此处](https://github.com/greglbd/angular-observer-pattern)的例子。这是一个 AngularJS factory,它实作了一个观察者模式的 service。它很适用于 ControllerAs 方法,如果正确使用的话,它比 `$scope.$watch` 运行更有效率,相比于 `$emit` 和 `$broadcast`,它更明确的对应唯一的 scope 或对象。 -**用例:**你可以使用此模式来在两个使用同一模型但互不关联的控制器之间通讯。 +**用例:**你可以通过此模式在两个使用同一模型但互不关联的控制器之间通讯。 From 2760d9ded04305d6cf08d76d29be6370c3e1294d Mon Sep 17 00:00:00 2001 From: Carlos Liu Date: Thu, 1 Oct 2015 00:14:41 +1300 Subject: [PATCH 22/29] Link to Chinese translation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 88450d8..6c8c929 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ - [Japanese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) by [morizotter](https://twitter.com/morizotter) - [Russian Translation](http://habrahabr.ru/post/250149/) - [French Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) by [manekinekko](https://github.com/manekinekko) +- [Chinese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-zh-cn.md) by [carlosliu](https://github.com/carlosliu) ## Abstract From 163495da45937bed14b96fbe70988bece560c3e9 Mon Sep 17 00:00:00 2001 From: "l.s.kramarov" Date: Fri, 30 Oct 2015 20:14:29 +0300 Subject: [PATCH 23/29] Russian translation was added --- README.md | 2 +- i18n/README-ru-ru.md | 1157 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1158 insertions(+), 1 deletion(-) create mode 100644 i18n/README-ru-ru.md diff --git a/README.md b/README.md index 6c8c929..be0ac66 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ ## Translations - [Japanese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) by [morizotter](https://twitter.com/morizotter) -- [Russian Translation](http://habrahabr.ru/post/250149/) +- [Russian Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ru-ru.md) by [l.s.kramarov](https://github.com/lskramarov) - [French Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) by [manekinekko](https://github.com/manekinekko) - [Chinese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-zh-cn.md) by [carlosliu](https://github.com/carlosliu) diff --git a/i18n/README-ru-ru.md b/i18n/README-ru-ru.md new file mode 100644 index 0000000..933006f --- /dev/null +++ b/i18n/README-ru-ru.md @@ -0,0 +1,1157 @@ +# Паттерны в AngularJS + + + +## Table of Contents + +* [Переводы](#переводы) +* [Краткий обзор](#праткий-обзор) +* [Введение](#Введение) +* [Краткий обзор AngularJS](#краткий-обзор-AngularJS) + * [Partials](#partials) + * [Controllers](#controllers) + * [Scope](#scope) + * [Directives](#directives) + * [Filters](#filters) + * [Services](#services) +* [AngularJS Patterns](#angularjs-patterns) + * [Services](#services-1) + * [Singleton](#singleton) + * [Factory Method](#factory-method) + * [Decorator](#decorator) + * [Facade](#facade) + * [Proxy](#proxy) + * [Active Record](#active-record) + * [Intercepting Filters](#intercepting-filters) + * [Directives](#directives-1) + * [Composite](#composite) + * [Interpreter](#interpreter) + * [Template View](#template-view) + * [Scope](#scope-1) + * [Observer](#observer) + * [Chain of Responsibilities](#chain-of-responsibilities) + * [Command](#command) + * [Controller](#controller-1) + * [Page Controller](#page-controller) + * [Others](#others) + * [Module Pattern](#module-pattern) + * [Data Mapper](#data-mapper) + * [Шаблон Observer как внешний сервис](#шаблон-observer-как-внешний-сервис) +* [Ссылки](#Ссылки) + + + +## Переводы + +- [Japanese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) by [morizotter](https://twitter.com/morizotter) +- [Russian Translation](http://habrahabr.ru/post/250149/) +- [French Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) by [manekinekko](https://github.com/manekinekko) +- [Chinese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-zh-cn.md) by [carlosliu](https://github.com/carlosliu) + +## Краткий обзор + +Один из лучших способов изучить, что то новое, это увидеть, как в нем используются уже знакомые нам вещи. Эта статья не намерена ознакомить читателей с проектированием или шаблонами проектирования. Она предлагает базовое понимание концепций ООП, шаблонов проектирования и архитектурных шаблонов. Цель статьи описать, как различные конструкции программного обеспечения и архитектурные шаблоны используются в AngularJS и написанных на нем SPA + +## Введение + +Статья начинается с краткого обзора фреймворка AngularJS. Обзор объясняет основные компоненты AngularJS: directives, filters, controllers, services, scope. Во втором разделе перечислены и описаны различные конструкции и архитектурные шаблоны, которые реализованы внутри фреймворка. Шаблоны сгруппированы по компонентам AngularJS, в которых они используются. Если некоторые шаблоны используются в нескольких компонентах, это будет указано. + +Последний раздел включает несколько архитектурных шаблонов, которые обычно используются в SPA построенных на AngularJS. + +## Краткий обзор AngularJS + +AngularJS это JavaScript веб фреймворк разработанный Google. Он намерен обеспечить прочную основу для разработки CRUD SPA . SPA загружается только один раз и не требует перезагрузки страницы при работе с ним. Это означает, что все ресурсы приложения (data, templates, scripts, styles) должны быть загружены при загрузке главной страницы или по требованию. Поскольку большинство CRUD приложений имеют общие характеристики и требования, AngularJS намерен предоставить их оптимальный набор из коробки. Вот несколько важных особенностей AngularJS: + +- two-way data binding (Двустороннее связывание данных) +- dependency injection, DI (Внедрение зависимостей) +- separation of concerns (Разделение ответственности) +- testability +- abstraction + +Разделение ответственности достигается путем деления каждого AngularJS приложения на отдельные компоненты, такие как: + +- partials +- controllers +- directives +- services +- filters + +Эти компоненты могут быть сгруппированы внутри различных модулей, которые помогают достичь более высокого уровня абстракции. Каждый компонент включает определенную часть логики приложения. + +### Partials + +Partials это HTML строки. Они могут содержать AngularJS выражения внутри элементов или их атрибутов. Одним из преимуществ AngularJS перед другими фреймворками, является то, что шаблоны AngularJS не находятся в промежуточном формате, который должен быть преобразован в HTML (как например в mustache.js) + +Вначале каждое SPA загружает файл Index.html. В случае с AngularJS этот файл содержит набор стандартных (и не очень) HTML атрибутов, элементов и комментариев, которые настраивают и запускают приложение. Каждое действие пользователя требует загрузки других partials (HTML строк или файлов с кусками HTML) или изменение состояния приложения, например через связывание данных (data binding) предоставленное фреймворком. + + +**Пример partials:** + +```HTML + + + + + + + + + +``` + +Вместе с выражениями AngularJS, partials определяют, какие действия должны быть выполнены для взаимодействия с пользователем. В примере выше значение атрибута ng-click означает, что метод changeFoo() будет вызван из текущего scope. + +### Controllers + +Контроллеры в AngularJS – это обычные функции, которые позволяют обрабатывать взаимодействие пользователя и приложения (например события мыши, клавиатуры и тд.) путем добавления методов в scope. Все внешние зависимости для контроллеров предоставляются при помощи механизма DI в AngularJS. Контроллеры также отвечают за взаимодействие моделей с partials путем добавления данных в scope. Это можно рассматривать как модель представления (view model). + +```JavaScript +function MyController($scope) { + $scope.buttonText = 'Click me to change foo!'; + $scope.foo = 42; + + $scope.changeFoo = function () { + $scope.foo += 1; + alert('Foo changed'); + }; +} +``` + +Например, если мы подключим контроллер представленный выше в предыдущую секцию, то пользователь будет иметь возможность взаимодействовать с приложением несколькими способами: + +1. Изменение "foo" путем ввода данных в поле ввода. Это немедленно отразится на значении "foo" из-за двустороннего связывания данных. +2. Изменение значения "foo" нажатием на кнопку с названием Click me to change foo!» + +Все пользовательские элементы, атрибуты, комментарии или классы могут быть директивами AngularJS (если они предварительно определены). + +### Scope + +В AngularJS scope является JavaScript объектом, который доступен для partials. Scope может включать различные свойства – примитивы, объекты или методы. Все методы добавленные в scope, могут быть вызваны с помощью выражений AngularJS внутри partials, которые связанны с данным scope или прямым вызовом метода любым компонентом, у которого есть ссылка на scope. С помощью соответствующих директив, данные добавляются в scope, и могут быть связаны с представлением, таким образом, каждое изменение в свойстве scope будет отражено в представлении и каждое изменение в представлении будет отображено в scope. + +Еще одной важной характеристикой scope в любом AngularJS приложении является то, что они связаны через механизм наследования прототипа (за исключением изолированных scope). Таким образом любой дочерний scope может использовать методы его родителя, так как это свойства его прямого или косвенного прототипа. + +Наследование scope показано в следующем примере: + +```HTML +
+
+ + +
+
+``` + +```JavaScript +function BaseCtrl($scope) { + $scope.foo = function () { + alert('Base foo'); + }; +} + +function ChildCtrl($scope) { + $scope.bar = function () { + alert('Child bar'); + }; +} +``` + +С div#child связан scope ChildCtrl, но поскольку scope ChildCtrl является вложенным в BaseCtrl, то все методы из BaseCtrl доступны в ChildCtrl, при помощи наследования прототипа и поэтому метод foo будет доступен при нажатии на кнопку button#parent-method. + +### Directives + +Директивы в AngularJS – это место где должны выполняться все манипуляции с DOM. Как правило если у вас в контроллере происходят манипуляции с DOM, то необходимо создать новую директиву или провести рефакторинг, который устранит манипуляции с DOM в контроллере. В простейшем случае у директивы есть имя и определение функции postLink, которая включает логику директивы. В более сложных случаях директива может содержать множество свойств, таких как: + +- template +- compile function +- link function +- etc... + +Директивы можно использовать в partials, например: + +```JavaScript +myModule.directive('alertButton', function () { + return { + template: '', + scope: { + content: '@' + }, + replace: true, + restrict: 'E', + transclude: true, + link: function (scope, el) { + el.click(function () { + alert(scope.content); + }); + } + }; +}); +``` + +```HTML +Click me +``` + +В примере выше тег будет заменен элементом button и при нажатии на кнопку, появиться предупреждение с текстом 42. + +### Filters + +Фильтры в AngularJS отвечают за инкапсуляцию логики, необходимой для форматирования данных. Обычно фильтры используются внутри partials, но так же через DI они доступны в контроллерах, директивах, сервисах или других фильтрах. + +Здесь простой пример фильтра, который преобразует строку в верхний регистр: + +```JavaScript +myModule.filter('uppercase', function () { + return function (str) { + return (str || '').toUpperCase(); + }; +}); +``` + +Внутри partials фильтры могут быть использованы с помощью синтаксиса Unix конвейеров (Unix's piping): + +```HTML +
{{ name | uppercase }}
+``` + +Внутри контроллера фильтр может быть использован следующим образом: + +```JavaScript +function MyCtrl(uppercaseFilter) { + $scope.name = uppercaseFilter('foo'); //FOO +} +``` + +### Services + +Любая часть логики, которая не относится к компонентам описанным выше, должна быть помещена в сервис. Обычно сервисы инкапсулируют специфическую область логики, неизменяемую логику, XHR, WebSockets и т. д. Когда контроллеры в приложении становятся слишком "толстыми", повторяющийся код должен быть вынесен в сервисы. + +```JavaScript +myModule.service('Developer', function () { + this.name = 'Foo'; + this.motherLanguage = 'JavaScript'; + this.live = function () { + while (true) { + this.code(); + } + }; +}); +``` + +Сервисы могут быть добавлены в любой компонент, который поддерживает DI (контроллеры, фильтры, директивы, другие сервисы): + +```JavaScript +function MyCtrl(Developer) { + var developer = new Developer(); + developer.live(); +} +``` + +## Паттерны AngularJS + +В двух последующих разделах, мы рассмотрим как традиционное проектирование и архитектурные шаблоны используются в компонентах AngularJS. + +В последней главе мы рассмотрим некоторые архитектурные шаблоны, которые часто используются при разработке SPA (и не только) на AngularJS. + +### Services + +#### Singleton + +>Singleton (одиночка) - шаблон проектирования, который ограничивает создание экземпляра класса одним объектом. Это полезно когда необходимо координировать действия во всей системе. Концепция подходит для систем, которые функционируют более эффективно, когда существует только один объект или когда экземпляры ограничены некоторым количеством объектов. + +UML диаграмма иллюстрирует шаблон singleton: + +![Singleton](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/singleton.svg "Fig. 1") + +Когда какому любо компоненту требуется зависимость, AngularJS разрешает ее используя следующий алгоритм: + +- Берет имя зависимости и делает поиск в хеш-карте, которая определяется в лексическом замыкании. (поэтому она имеет приватную область видимости). +- Если зависимость найдена AngularJS передает ее как параметр компонента. +- >Если зависимость не найдена: + - AngularJS создает ее при помощи вызова фабричного метода или его провайдера (т. е. $get). Обратите внимание, что создание зависимости может потребовать рекурсивный вызов по тому же алгоритму для определения всех зависимостей данной зависимости. Этот процесс может привести к циклической зависимости. + - AngularJS кеширует ее внутри хеш-карты упомянутой выше. + - AngularJS передает ее в качестве параметра компонента, для которого она указана. + +Лучше взгляните на исходный код AngularJS, который реализует getService: + +```JavaScript +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(); + } + } +} +``` + +Представьте, что каждый service это singleton, потому что service создается только один раз. Кэш можно рассматривать, как менеджера singleton. Так же эта реализация немного отличается от той, что представлена в UML диаграмме, потому что вместо создания статической приватной ссылки на singleton внутри его конструктора, мы сохраняем ссылку внутри менеджера singleton. + +Таким образом, service это фактически singleton, но реализован не через шаблон singleton, тем самым обеспечивая некоторые преимущества по сравнению со стандартной реализацией: + +- улучшает тестируемость кода +- можно управлять созданием объектов singleton (В данном случае IoC контейнер контролирует создание объектов используя ленивые вызовы.) + +Для более детального рассмотрения данной темы можете ознакомиться со статьей Misko Hevery's в блоге Google Testing. + +#### Factory Method + +>Factory Method (Фабричный метод так же известен как Виртуальный конструктор (англ. Virtual Constructor)) — порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне. + +![Factory Method](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/factory-method.svg "Fig. 2") + +Давайте рассмотрим следующий фрагмент: + +```JavaScript +myModule.config(function ($provide) { + $provide.provider('foo', function () { + var baz = 42; + return { + //Factory method + $get: function (bar) { + var baz = bar.baz(); + return { + baz: baz + }; + } + }; + }); +}); + +``` + +Здесь для определения нового "provider" используется функция обратного вызова config. "Provider" это объект с методом $get. Поскольку в JavaScript нет интерфейсов и язык использует "утиную" типизацию, то договорились фабричный метод в "provider" называть $get. + +Каждый service, filter, directive и controller имеют provider (т. е объект, который имеет фабричный метод $get), он отвечает за создание экземпляров компонента. + +Мы можем копнуть немного глубже в реализацию AngularJS: + +```JavaScript +//... + +createInternalInjector(instanceCache, function(servicename) { + var provider = providerInjector.get(servicename + providerSuffix); + return instanceInjector.invoke(provider.$get, provider, undefined, servicename); +}, strictDi)); + +//... + +function invoke(fn, self, locals, serviceName){ + if (typeof locals === 'string') { + serviceName = locals; + locals = null; + } + + var args = [], + $inject = annotate(fn, strictDi, serviceName), + length, i, + key; + + for(i = 0, length = $inject.length; i < length; i++) { + key = $inject[i]; + if (typeof key !== 'string') { + throw $injectorMinErr('itkn', + 'Incorrect injection token! Expected service name as string, got {0}', key); + } + args.push( + locals && locals.hasOwnProperty(key) + ? locals[key] + : getService(key) + ); + } + if (!fn.$inject) { + // this means that we must be an array. + fn = fn[length]; + } + + return fn.apply(self, args); +} +``` + +В примере вы можете видеть как в действительности используется метод $get: + +```JavaScript +instanceInjector.invoke(provider.$get, provider, undefined, servicename) +``` + +В фрагменте выше вызывается метод invoke и ему в качестве первого аргумента передается фабричным метод ($get) сервиса. Внутри метода invoke вызывается функция annotate, которой в качестве первого аргумента так же передается фабричный метод. Функция annotate разрешает все зависимости, через механизм DI AngularJS (рассматривался выше). После разрешения всех зависимостей, вызывается фабричный метод: + +Если рассуждать в терминах описанной выше UML диаграммы, то мы можем вызвать Creator, который через фабричный метод вызовет "ConcreteCreator" который создаст "Product". + +В данном случае мы получаем некоторые преимущества используя шаблон фабричный метод, потому что используется косвенное создание экземпляров. Таким образом фреймворк влияет на макеты/шаблоны создания новых компонентов, потому что: + +- это наиболее подходящий момент, когда нужно создать компонент +- разрешить все зависимости компонента +- управлять количеством разрешенных экземпляров компонента (для service и filter один, но много для controller) + +#### Decorator + +>Decorator (Декоратор) — структурный шаблон проектирования, предназначенный для динамического добавления дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу создания подклассов с целью расширения функциональности. + +![Decorator](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/decorator.svg "Fig. 4") + +AngularJS из коробки предоставляет возможности для расширения и/или повышения функциональности уже существующих service' ов. Используя метод decorator или $provide вы можете создать обертку для любого service, уже определенного вами или из сторонней библиотеки: + +```JavaScript +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; + }); +}); +``` +Приведенный выше пример определяет новый service с именем "foo". В функции обратного вызова "config" вызывается метод $provide.decorator и ему в качестве первого аргумента передается имя service, который мы хотим декорировать, вторым аргументом передается функция, она собственно и реализует декоратор. $delegate хранит ссылку на оригинальный service foo. Мы декорируем service переопределяя его метод bar. Фактически декорирование просто расширение bar, путем включения еще одного состояния console.log - console.log('Decorated') и после чего вызываем оригинальный метод bar в соответствующем контексте. + +Использование шаблона особенно полезно, когда нам нужно, изменить функциональность service созданных третьими лицами. В тех случаях, когда необходимы многочисленные декораторы (например, при измерении производительности методов, авторизации, регистрации и т.д.) может получиться много дублируемого кода и нарушение принципа DRY. В таких случаях целесообразно использовать аспектно-ориентированое программирование. АОП фреймворк для AngularJS можно найти на github.com/mgechev/angular-aop. + +#### Facade + +>Шаблон Facade (фасад) — структурный шаблон проектирования, позволяющий скрыть сложность системы путем сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы. +Facade может: + +>1. Сделать более легким использование библиотек, понимание и тестирование, так как facade имеет более подходящие методы для выполнения общих задач + +>2. Cделать библиотеку более читабельной, по той же причине + +>3. Уменьшить зависимость внутренних библиотек от внешнего кода, поскольку большая часть кода использует facade, это позволяет разрабатывать систему более гибко + +>4. Обернуть плохо спроектированную коллекцию API-интерфейсов, одной хорошо спроектированной (в соответствии с потребностями задачи) + +![Facade](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/facade.svg "Fig. 11") + +В AngularJS есть несколько facade' ов. Каждый раз когда вы хотите предоставить высокоуровневый API для некоторой функциональности, вы практически создаете фасад. + +К примеру, давайте посмотрим как мы можем создать XMLHttpRequest POST запрос: + +```JavaScript +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); +``` +Если мы хотим отправить данные используя AngularJS $http сервис, мы можем: + +```JavaScript +$http({ + method: 'POST', + url: '/example/new', + data: data +}) +.then(function (response) { + alert(response); +}); +``` +или даже: + +```JavaScript +$http.post('/someUrl', data) +.then(function (response) { + alert(response); +}); +``` +Второй вариант представляет предварительно настроенную версию, которая создает HTTP POST запрос. + +Третий вариант, это высокоуровневая абстракция созданная с помощью $resource service и построена поверх $http service. Мы еще раз рассмотрим этот service в разделах Active Record и Proxy. + +#### Proxy + +>Proxy (Заместитель) — структурный шаблон проектирования, предоставляет объект, который контролирует доступ к другому объекту, перехватывая все вызовы (выполняет функцию контейнера). Proxy может взаимодействовать с чем угодно: сетевым соединением, большим объектом в памяти, файлом или другими ресурсами, которые дорого или невозможно копировать. + +![Proxy](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/proxy.svg "Fig. 9") + +Мы можем различать три типа прокси: + +- Virtual Proxy +- Remote Proxy +- Protection Proxy + +В этом подразделе мы рассмотрим реализованный в AngularJS Virtual Proxy. + +В фрагменте ниже, мы вызываем метод get объекта $resource с именем User: + +```JavaScript +var User = $resource('/users/:id'), + user = User.get({ id: 42 }); +console.log(user); //{} +``` + +console.log выведет пустой объект. Так как AJAX запрос выполнится асинхронно после вызова User.get, и во время вызова console.log у нас не будет данных пользователя. Сразу после вызова User.get, выполняется GET запрос, он возвращает пустой объект и сохраняет ссылку на него. Мы можем представить себе этот объект как virtual proxy, он будет заполнен данными как только клиент получит ответ от сервера. + +Как это работает в AngularJS? Давайте рассмотрим следующий фрагмент: + +```JavaScript +function MainCtrl($scope, $resource) { + var User = $resource('/users/:id'), + $scope.user = User.get({ id: 42 }); +} +``` + +```html + +``` +После выполнения фрагмента кода, показанного выше, свойство user объекта $scope будет пустым объектом ({}), это означает, что user.name будет undefined и не будет отображено. После того как сервер вернет ответ для GET запроса, AngularJS заполнит этот объект данными, полученными с сервера. В течении следующего $digest цикла, AngularJS обнаружит изменения в $scope.user и это приведет к обновлению представления. + +#### Active Record + +>Active Record – это объект, который хранит данные и поведение. Обычно большинство данных в этом объекте постоянны, обязанность объекта Active Record заботиться о связи с базой данных для создания, обновления, поиска и удаления данных. Он может делегировать эту ответственность на объекты более низкого уровня, но вызов объекта Active Record или его статических методов, приведет к взаимодействию с БД. + +![Active Record](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/active-record.svg "Fig. 7") + +В AngularJS определен service $resource. В текущей версии AngularJS (1,2+) он распространяется отдельным модулем и не включен в ядро. +В соответствии с документацией $resource: + +>$resource - это фабрика для создания объектов $resource, которые позволяют взаимодействовать с RESTfull источниками данных на стороне сервера. Объект $resource имеет методы, которые предоставляют высокоуровневое поведение, без необходимости взаимодействовать с низкоуровневым service' ом $http. + +Здесь показано, как можно использовать $resource: + +```JavaScript +var User = $resource('/users/:id'), + user = new User({ + name: 'foo', + age : 42 + }); + +user.$save(); +``` + +Вызов $resource создает конструктор для экземпляров нашей модели. Каждый из экземпляров модели будет иметь методы, которые могут быть использованы для различных операций CRUD. + +Таким образом мы можем использовать функцию конструктор и его статические методы: + +```JavaScript +User.get({ userid: userid }); +``` + +Код выше будет сразу возвращать пустой объект и сохранять ссылку на него. После того, как будет получен и проанализирован ответ, AngularJS заполнит объект полученными данными (см. proxy). +Вы можете найти более детальную документацию по "магии" объекта $resource и AngularJS. +Так Мартин Фаулер утверждает что: + +>Объект Active Record должен заботиться о связи с БД, для того что бы создавать… +$resource не реализует полного шаблона Active Record, так как он взаимодействует с RESTful сервисом вместо БД. Во всяком случае мы можем рассматривать его как "Active Record для взаимодействия с RESTful". + +#### Intercepting Filters + +>Создает цепочку фильтров для выполнения простых задач пред/пост обработки запросов. + +![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/intercepting-filters.svg "Fig. 3") + +В некоторых случаях вам нужно будет сделать какую либо пред/пост обработку HTTP запросов. В данном случае Intercepting Filters предназначены для пред/пост обработки HTTP запросов/ответов, для ведения журнала, безопасности или любой другой задачи, которой необходимо тело или заголовок запроса. Обычно шаблон Intercepting Filters включает цепочку фильтров, каждый из которых обрабатывает данные в определенном порядке. Выход каждого фильтра является входом для следующего. + +В AngularJS мы имеем представление о Intercepting Filters в $httpProvider. $httpProvider имеет свойство interceptors (перехватчики), оно включает список объектов. У каждого объекта есть свойства: request, response, requestError, responseError. + +requestError вызывается если в предыдущем перехватчике произошла ошибка или он был отклонен, соответственно responseError вызывается, когда предыдущий перехватчик ответа возбудил исключение. + +Ниже базовый пример, как вы можете добавить перехватчики используя литерал объекта: + +```JavaScript +$httpProvider.interceptors.push(function($q, dependency1, dependency2) { + return { + 'request': function(config) { + // same as above + }, + 'response': function(response) { + // same as above + } + }; +}); +``` + +### Directives + +#### Composite + +>Шаблон composite (компоновщик) – структурный шаблон проектирования. Шаблон composite описывает, как группировать объекты, что бы к ним можно было обращаться, так же как к одному объекту. Цель composite составить объекты в древовидную структуру, для представления иерархии от частного к целому. + +![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/composite.svg "Fig. 3") + +Согласно "Банде четырех", MVC ни что иное как сочетание: + +- Strategy +- Composite +- Observer + +Они утверждают что представление является композицией компонентов. В AngularJS аналогичная ситуация. Представления формируются композицией директив и элементов DOM, на которых эти директивы построены. + +Давайте посмотрим на следующий пример: + +```HTML + + + + + + + Zippy! + + + +``` + +```JavaScript +myModule.directive('zippy', function () { + return { + restrict: 'E', + template: '
', + link: function (scope, el) { + el.find('.header').click(function () { + el.find('.content').toggle(); + }); + } + } +}); +``` + +В этом примере создается директива, которая является компонентом пользовательского интерфейса. Созданный компонент (с именем “zippy”) имеет заголовок и содержание. При нажатии на его заголовок переключается видимость его содержимого. + +Из первого примера мы можем заметить, что дерево DOM это композиция элементов. Корневой компонент это html, сразу же за ним следуют вложенные элементы head, body и так далее… + +Во втором примере, мы видим, что свойство директивы template содержит разметку с директивой ng-transclude. Это означает, что внутри директивы “zippy” может быть еще одна директива ng-transclude т. е. композиция директив. Теоретически мы можем делать бесконечную вложенность компонентов пока не достигнем конечного узла. + +#### Interpreter + +>Interpreter (интерпретатор) – поведенческий шаблон проектирования, указывает, как определять выражения в языке. Основная идея, классифицировать каждый символ (терминальный или не терминальный) в специализированном языке программирования. Синтаксическое дерево (пример шаблона композиция) выражений используется для анализа (интерпретирования) выражения. + +![Interpreter](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/interpreter.svg "Fig. 6") + +С помощью $parse, AngularJS предоставляет свою собственную реализацию интерпретатора DSL (Domain Specific Language). Использование DSL упрощает и изменяет JavaScript. Основная разница между AngularJS и JavaScript выражениях в том что AngularJS выражения: + +- могут включать фильтры с UNIX подобным синтаксисом +- не возбуждают никаких исключений +- не имеют никакого управления потоком состояния (исключения, циклы, условия так же могут использовать тернарный оператор) +- выполняются в полученном контексте (контекст текущего $scope) + +Внутри $parse определены два основных компонента: + +```JavaScript +//Responsible for converting given string into tokens +var Lexer; +//Responsible for parsing the tokens and evaluating the expression +var Parser; +``` + +При получении выражения, оно разбивается на лексемы и кэшируется (из-за проблем с производительностью). + +Терминальные выражения в AngularJS DSL определены следующим образом: + +```JavaScript +var OPERATORS = { + /* jshint bitwise : false */ + 'null':function(){return null;}, + 'true':function(){return true;}, + 'false':function(){return false;}, + undefined:noop, + '+':function(self, locals, a,b){ + //... + }, + '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, + '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, + '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, + '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, + '=':noop, + '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, + '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, + '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, + '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, + '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, + '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, + '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, + '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, + '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, + '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, + '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, + '!':function(self, locals, a){return !a(self, locals);} +}; +``` + +Каждую функцию связанную с каждым терминальным символом, можно представить как реализацию интерфейса AbstractExpression (абстрактных выражений). + +Клиент интерпретирует полученное выражение в определенном контексте – определенного $scope. + +Несколько простых выражений AngularJS: + +```JavaScript +// toUpperCase filter is applied to the result of the expression +// (foo) ? bar : baz +(foo) ? bar : baz | toUpperCase +``` + +#### Template View + +> Преобразует данные в HTML путем встраивания маркеров в HTML страницу. + +![Template View](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/template-view.svg "Fig. 8") + +The dynamic page rendering is not that trivial thing. It is connected with a lot of string concatenations, manipulations and frustration. Far easier way to build your dynamic page is to write your markup and embed little expressions inside it, which are lately evaluated in given context and so the whole template is being compiled to its end format. In our case this format is going to be HTML (or even DOM). This is exactly what the template engines do - they take given DSL, evaluate it in the appropriate context and then turn it into its end format. + +Динамическое отображение страницы не такая простая задача. Это связано с большим количеством конкатенации строк, манипуляций и проблем. Наиболее простой способ построить динамическую страницу - это написать свою разметку и включить в нее несколько выражений, которые будут обработаны в определенном контексте, в результате весь шаблон будет преобразован в конечный формат. В нашем случае этим форматом будет HTML (или даже DOM). Это именно то что делают шаблонизаторы – они получают DSL, обрабатывают его в соответствующем контексте и затем преобразуют в конечный формат. + +Шаблоны очень часто используются на back-end. Например вы можете вставлять PHP код внутри HTML и создавать динамические страницы, так же вы можете использовать Smarty или eRuby в Ruby, чтобы вставлять Ruby код в статические страницы. + +В JavaScript есть много шаблонизаторов, таких как mustache.js, handlebars и т.д. Большинство из них работают с шаблоном как со строкой. Шаблон может храниться различными способами: + +- статический файл, который будет получен AJAX’ом +- скрипт встроенный внутри вашего представления +- строка внутри вашего JavaScript кода + +К примеру: + +```html + +``` + +Шаблонизатор превращает строку в элементы DOM, путем объединения ее с полученным контекстом. Таким образом все встроенные в разметку выражения анализируются и заменяются их значениями. + +Например, если мы обработаем шаблон показанный выше в контексте объекта: { names: ['foo', 'bar', 'baz'] } то получим: + +```html +

Names

+ foo + bar + baz +``` + +На самом деле шаблоны в AngularJS это обычный HTML. Они не находятся в промежуточном формате, как большинство шаблонов. Что делает AngularJS компилятор, что бы обойти дерево DOM и найти уже известные директивы (элементы, атрибуты, классы или даже комментарии) ? Когда AngularJS находит любую из этих директив он вызывает логику связанную с ней, она может включать определение различных выражений в контексте текущего $scope. + +К примеру: + + +```html +
    +
  • {{name}}
  • +
+``` + +В контексте scope: + +```javascript +$scope.names = ['foo', 'bar', 'baz']; +``` + +Будет получен тот же результат что и выше. Главное отличие в том, что шаблон расположен не внутри тега script, здесь это просто HTML. + + +### Scope + +#### Observer + +>Шаблон observer (наблюдатель) – поведенческий шаблон проектирования, в котором объект называемый субъектом, хранит список своих зависимостей, которые называются наблюдателями и уведомляет их при каких либо изменениях состояния, обычно вызовом одного из их методов. В основном используется для реализации распределенных систем обработки событий. + +![Observer](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/observer.svg "Fig. 7") + +В приложениях AngularJS есть два основных способа взаимодействия между scope. + +Первый, вызов методов родительского scope из дочернего scope. Это возможно, потому что дочерний scope наследует прототип своего родителя, как упоминалось выше (смотреть Scope). Это позволяет осуществлять одностороннее взаимодействие от ребенка к родителю. Иногда необходимо вызвать метод дочернего scope или уведомить его о возникновении события в родительском scope. AngularJS предоставляет встроенный шаблон наблюдатель, который позволяет делать это. + +Второй возможный вариант использования шаблона observer, когда несколько scope подписаны на события, но scope в котором оно возникает ничего о нем не знает. Это позволяет уменьшить связанность scope' ов, они не должны ничего знать о других scope. + +Каждый scope в AngularJS имеет публичные методы $on, $emit и $broadcast. Метод $on в качестве аргументов принимает имя события и функцию обратного вызова. Эту функцию можно представить как наблюдателя – объект который реализует интерфейс observer (В JavaScript все функции первого класса, поэтому мы можем обеспечить только реализацию метода уведомления). + +```JavaScript +function ExampleCtrl($scope) { + $scope.$on('event-name', function handler() { + //body + }); +} +``` + +Таким образом текущий scope подписывается на событие "event-name". Когда оно возникнет в любом из родительских или дочерних scope, будет вызван обработчик. + +Методы $emit и $broadcast используются для запуска событий, соответственно вверх и вниз по цепочке наследования. + +К примеру: + +```JavaScript +function ExampleCtrl($scope) { + $scope.$emit('event-name', { foo: 'bar' }); +} +``` + +В примере выше scope генерирует событие "event-name" вверх для всех scope. Это значит что каждый родительский scope, который подписан на событие "event-name", будет уведомлен и его обработчик будет вызван. + +Тоже самое происходит когда вызывается метод $broadcast. Разница лишь в том, что событие будет передаваться вниз – для всех дочерних scope. Каждый scope может подписаться на любое событие с несколькими функциями обратного вызова (т. е. он может связать несколько наблюдателей с данным событием). + +В JavaScript сообществе этот шаблон более известен как публикация/подписка. + +For a best practice example see [Observer Pattern as an External Service](#observer-pattern-as-an-external-service) + +#### Chain of Responsibilities + +>Chain of Responsibilities (цепочка обязанностей) – поведенческий шаблон проектирования, состоит из объекта команды и последовательности обрабатывающих объектов (обработчиков). Каждый обработчик содержит логику, определяющую тип команды, которую он может обработать. Затем команда поступают к следующему обработчику в цепочке. Шаблон так же содержит механизм для добавления новых обработчиков в конец этой цепочки. + +![Chain of Responsibilities](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") + +Как показано выше, scope образует иерархию, известную как цепочка scope. Некоторые из этих scope изолированы, это значит, что они не наследуют прототипы своих родительских scope, но связаны с ним через его свойство $parent. + +После вызова $emit или $broadcast возникает событие, которое начинает движение (вниз или вверх в зависимости от вызванного метода) по цепочке scope, ее можно представить как шину событий или точнее как цепочку обязанностей. Каждый последующий scope может: + +- Обработать событие и передать его следующему scope в цепочке +- Обработать событие и остановить его распространение +- Пропустить событие к следующему scope в цепочке, без его обработки +- Остановить распространение события без его обработки + +В примере ниже, ChildCtrl генерирует событие, которое распространяется вверх по цепочке scope. Здесь каждый родительский scope (ParentCtrl и MainCtrl) должны обработать событие записав в консоль: "foo received". Если какой нибудь из scope должен быть конечным получателем, он может вызвать метод stopPropagation в объекте события. + +```JavaScript +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'); +}); +``` + +Обработчики в показанной выше UML диаграмме, являются различными scope, добавленными в контроллер. + +#### Command + +>Command - поведенческий шаблон проектирования, в котором объект используется для инкапсуляции всей необходимой информации и вызова метода через некоторое время. Эта информация включает имя метода, объект которому принадлежит метод и значения параметров метода. + +![Command](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/command.svg "Fig. 11") + +В AngularJS шаблон Command позволяет описать реализацию связывания данных (data binding). + +Когда мы хотим связать нашу модель с представлением мы можем использовать директиву ng-bind (для одностороннего связывания данных) и ng-model (для двустороннего связывания данных). К примеру, если мы хотим, что бы каждое изменение модели отображалось в представлении: + +```html + +``` + +Теперь каждый раз когда мы изменяем значение foo, текст внутри тега span тоже будет изменен. Также можно использовать более сложные выражения: + +```html + +``` + +В примере выше значением тега span будет сумма значений foo и bar в верхнем регистре. Что происходит внутри ? +В каждом scope есть метод $watch. Когда компилятор AngularJS находит директиву ng-bind, он создает нового наблюдателя для выражения foo + ' ' + bar | uppercase, ($scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });). Функция обратного вызова будет вызвана каждый раз когда изменяется значение выражения. В данном случае функция обратного вызова будет обновлять значение тега span. + +Вот несколько первых строк реализации $watch: + +```javascript +$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 + }; +//... +``` + +Мы можем представить watcher как Command. Выражение Command будет вычисляться в каждом цикле "$digest". Как только AngularJS обнаружит изменение выражения, он вызовет функцию слушателя. Watcher включает всю необходимую информацию для наблюдения и делегирования выполнение команды слушателю (фактическому получателю). Мы можем представить $scope как Client и цикл $digest как Invoker команд. + +### Controllers + +#### Page Controller + +>Объект, который обрабатывает запросы определенной страницы или действия на сайте. Мартин Фаулер. + +![Page Controller](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/page-controller.svg "Fig. 8") + +Согласно [4](#Ссылки) контроллерами на страницу: + +>Шаблон контроллер страницы допускает ввод данных с полученной страницы, выполнение запрошенных действий для модели и определяет правильное представление для результирующей страницы. Разделение диспетчерской логики от остального кода представления. + +Из-за большого количества дублируемого кода на страницах (к примеру футеры, хедеры, код заботящийся о сессии пользователя) контроллеры могут образовывать иерархию. В AngularJS есть контроллеры, которые ограничены scope. Они не принимают запросы пользователей, так как это ответственность $route или $state, а за отображение отвечают директивы ng-view/ui-view. + +Так же как в контроллерах страницы, контроллеры AngularJS отвечают за взаимодействие с пользователем, обеспечивая обновление моделей. Эти модели после добавления в scope не защищены от просмотра, все методы включенные в представление в итоге становятся действиями пользователя (методы scope). Другое сходство между контроллерами страниц и AngularJS контроллерами – это иерархия, которую они образуют. Этому соответствует иерархия scope видимости. Таким образом простыми действиями мы можем изолировать базовые контроллеры. + +Контроллеры AngularJS очень похожи на ASP.NET WebForms, так как их обязанности практически одинаковы. Вот пример иерархии между несколькими контроллерами: + +```HTML + + + + + +
+ {{user.name}} + +
+ + +``` + +```JavaScript +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); +} +``` + +Этот пример иллюстрирует самый простой способ повторного использования логики с помощью базового контроллера, но в любом случае в "production" приложениях не рекомендуется помещать логику авторизации в контроллеры. Доступ к различным маршрутам можно определить на более высоком уровне абстракции. + +ChildCtr отвечает за отображение модели в представлении и обработку действий, таких как нажатие на кнопку с названием «Click». + +### Others + +#### Module Pattern + +На самом деле это не шаблон проектирования от "банды четырех" и даже ни один из "Patterns of Enterprise Application Architecture". Это традиционный JavaScript шаблон проектирования, целью которого является обеспечение инкапсуляции. +Используя шаблон модуль вы можете получить приватность данных на основе функционально-лексической области видимости. Каждый модуль может иметь ноль и более приватных методов, которые скрыты в локальной области видимости. + +Эта функция возвращает объект, который предоставляет публичный API для данного модуля: + +```javascript +var Page = (function () { + + var title; + + function setTitle(t) { + document.title = t; + title = t; + } + + function getTitle() { + return title; + } + + return { + setTitle: setTitle, + getTitle: getTitle + }; +}()); +``` + +В примере выше IIFE (Immediately-Invoked Function Expression - выражение немедленно вызванной функции), которая после вызова возвращает объект, с двумя методами (setTitle и getTitle). Возвращенный объект присвоен переменной Page. В таком случае пользователь объекта Page не имеет прямого доступа к переменной title, которая определена внутри локальной области видимости IIFE. + +Шаблон модуль очень полезен при определении service в AngularJS. Используя этот шаблон мы можем достичь (и на самом деле достигаем) приватности: + +```javascript +app.factory('foo', function () { + + function privateMember() { + //body... + } + + function publicMember() { + //body... + privateMember(); + //body + } + + return { + publicMember: publicMember + }; +}); +``` + +После того как мы добавим foo в любой другой компонент, у нас не будет возможности использовать приватные методы, только публичные. Это решение является довольно мощным, особенно, когда мы создаем библиотеки для повторного использования. + +#### Data Mapper + +>Шаблон Data Mapper – это слой доступа к данным, который выполняет двунаправленную передачу данных между постоянным хранилищем данных (часто это реляционная БД) и данными в памяти. Цель этого шаблона – хранить в памяти представление постоянных данных, независимо друг от друга и от себя самого. + +![Data Mapper](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/data-mapper.svg "Fig. 10") + +Как уже говорилось Data Mapper используется для двунаправленной передачи данных между постоянным хранилищем данных и данными в памяти. Обычно AngularJS приложение взаимодействует с API сервера, которое написано на любом серверном языке (Ruby, PHP, Java, JavaScript и т.д.). + +Если у нас есть RESTful API, service $resource поможет нам взаимодействовать с сервером в Active Record подобном стиле. Хотя некоторые приложения возвращают данные с сервера не в самом подходящем формате, который мы хотим использовать на front-end. + +Давайте предположим что в нашем приложении, у каждого пользователя есть: + +- name +- address +- list of friends + +И API у которого есть следующие методы: + +- `GET /user/:id` - возвращает имя и адрес полученного пользователя +- `GET /friends/:id` - возвращает список друзей данного пользователя + +Одно из решений, использовать два различных service, один для первого метода и другой для второго. Возможно более подходящим решением будет, использование одного service, который называется User, он загружает друзей пользователя, когда мы запрашиваем User: + +```javascript +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; +}); +``` + +Таким образом мы создали псевдо Data Mapper, который адаптируется к нашему API в соответствии с требованиями SPA. + +Мы можем использовать User следующим образом: + +```javascript +function MainCtrl($scope, User) { + User.get({ id: 1 }) + .then(function (data) { + $scope.user = data; + }); +} +``` + +И соответствующий шаблон: + +```html +
+
+ Name: {{user.name}} +
+
+ Address: {{user.address}} +
+
+ Friends with ids: +
    +
  • {{friend}}
  • +
+
+
+``` + +#### Шаблон Observer как внешний сервис + +##### Описание + +Пример ниже взят отсюда [here](https://github.com/greglbd/angular-observer-pattern). Здесь фабрика создает сервис, который реализует шаблон Observer. +Этот шаблон хорошо работает при использовании синтаксиса ControllerAs и может быть более эффективным чем `$scope.$watch`, так же Observer будет более специфичным для конкретного scope или объекта, когда они правильно используют $emit and $broadcast + +**Вариант использования:** Вы можете использовать этот шаблон для взаимодействия двух контроллеров, которые обращаются к одной модели, но не подключены друг к другу. + +##### Пример контроллера + +Пример ниже показывает как подписаться, сгенерировать и отписаться от события. + +```javascript +angular.module('app.controllers') + .controller('ObserverExample', ObserverExample); +ObserverExample.$inject= ['ObserverService', '$timeout']; + +function ObserverExample(ObserverService, $timeout) { + var vm = this; + var id = 'vm1'; + + ObserverService.attach(callbackFunction, 'let_me_know', id) + + function callbackFunction(params){ + console.log('now i know'); + ObserverService.detachByEvent('let_me_know') + } + + $timeout(function(){ + ObserverService.notify('let_me_know'); + }, 5000); +} +``` +Еще один способ удалить событие + +```javascript +angular.module('app.controllers') + .controller('ObserverExample', ObserverExample); +ObserverExample.$inject= ['ObserverService', '$timeout', '$scope']; + +function ObserverExample(ObserverService, $timeout, $scope) { + var vm = this; + var id = 'vm1'; + ObserverService.attach(callbackFunction, 'let_me_know', id) + + function callbackFunction(params){ + console.log('now i know'); + } + + $timeout(function(){ + ObserverService.notify('let_me_know'); + }, 5000); + + // Cleanup listeners when this controller is destroyed + $scope.$on('$destroy', function handler() { + ObserverService.detachByEvent('let_me_know') + }); +} +``` + +## Ссылки + +1. [Wikipedia](https://en.wikipedia.org/wiki). Краткое описание шаблонов проектирования в Википедии. +2. [AngularJS' documentation](https://docs.angularjs.org) +3. [AngularJS' git repository](https://github.com/angular/angular.js) +4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) +5. [Patterns of Enterprise Application Architecture (P of EAA)](http://martinfowler.com/books/eaa.html) +6. [Using Dependancy Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) +7. [Why would one use the Publish/Subscribe pattern (in JS/jQuery)?](https://stackoverflow.com/questions/13512949/why-would-one-use-the-publish-subscribe-pattern-in-js-jquery) From e879fbfc9dbd821a4c2c04a3035d8a1608d1552c Mon Sep 17 00:00:00 2001 From: mgechev Date: Sun, 1 Nov 2015 10:27:12 +0200 Subject: [PATCH 24/29] Update russian translations links --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index be0ac66..2f18b25 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,8 @@ ## Translations - [Japanese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) by [morizotter](https://twitter.com/morizotter) -- [Russian Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ru-ru.md) by [l.s.kramarov](https://github.com/lskramarov) +- [Russian Translation 1](http://habrahabr.ru/post/250149/) +- [Russian Translation 2](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ru-ru.md) by [l.s.kramarov](https://github.com/lskramarov) - [French Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) by [manekinekko](https://github.com/manekinekko) - [Chinese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-zh-cn.md) by [carlosliu](https://github.com/carlosliu) From 3dba1a85721c9ccad2956fd1cffe0d8998d4edf4 Mon Sep 17 00:00:00 2001 From: Alexander Todorov Date: Thu, 5 Nov 2015 01:32:56 +0200 Subject: [PATCH 25/29] Typo fixes --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2f18b25..3eb7653 100644 --- a/README.md +++ b/README.md @@ -583,7 +583,7 @@ You can find more details for `$resource` [The magic of $resource](http://blog.m Since Martin Fowler states that -> responsibility of the Active Record object is to take care of the communication with the databse in order to create... +> responsibility of the Active Record object is to take care of the communication with the database in order to create... `$resource` does not implements exactly the Active Record pattern, since it communicates with RESTful service instead of the database. Anyway, we can consider it as "Active Record like RESTful communication". @@ -1156,10 +1156,10 @@ function ObserverExample(ObserverService, $timeout, $scope) { ## References -1. [Wikipedia](https://en.wikipedia.org/wiki). The source of all brief descriptions of the design patterns is wikipedia. +1. [Wikipedia](https://en.wikipedia.org/wiki). The source of all brief descriptions of the design patterns is Wikipedia. 2. [AngularJS' documentation](https://docs.angularjs.org) 3. [AngularJS' git repository](https://github.com/angular/angular.js) 4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) 5. [Patterns of Enterprise Application Architecture (P of EAA)](http://martinfowler.com/books/eaa.html) -6. [Using Dependancy Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) +6. [Using Dependency Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) 7. [Why would one use the Publish/Subscribe pattern (in JS/jQuery)?](https://stackoverflow.com/questions/13512949/why-would-one-use-the-publish-subscribe-pattern-in-js-jquery) From 55812434b20b9f0d277075fa20ba8cc796c3ceb6 Mon Sep 17 00:00:00 2001 From: Matthias Thoemmes Date: Sun, 20 Dec 2015 17:26:36 +0100 Subject: [PATCH 26/29] Refactoring Gulpfile using dependency injection --- gulp/modules/generic-task.js | 38 ++++++++++++++ gulp/tasks/clean.js | 7 +++ gulp/tasks/copy-images.js | 7 +++ gulp/tasks/copy-md.js | 15 ++++++ gulp/tasks/default.js | 5 ++ gulp/tasks/i18n.js | 13 +++++ gulpfile.js | 98 ++++++------------------------------ package.json | 15 ++++-- 8 files changed, 110 insertions(+), 88 deletions(-) create mode 100644 gulp/modules/generic-task.js create mode 100644 gulp/tasks/clean.js create mode 100644 gulp/tasks/copy-images.js create mode 100644 gulp/tasks/copy-md.js create mode 100644 gulp/tasks/default.js create mode 100644 gulp/tasks/i18n.js diff --git a/gulp/modules/generic-task.js b/gulp/modules/generic-task.js new file mode 100644 index 0000000..e7f3519 --- /dev/null +++ b/gulp/modules/generic-task.js @@ -0,0 +1,38 @@ +module.exports = function (gulp, markdownpdf, path, log, chalk, rename, TITLE) { + + function genericTask(lang){ + + gulp.task('generate:pdf:' + lang, function() { + + var files = ['./temp/*.md']; + if (lang === 'eng'){ + files = './temp/README.md'; + } + else if(lang !== 'all'){ + files = ['./temp/*-'+lang+'.md']; + } + + return gulp.src(files) + .pipe(markdownpdf({ + cwd: path.resolve('./temp/'), + layout: 'github' + })) + .on('error', function(err){ + log(chalk.red('doc task failed'), err); + }) + .pipe(rename(function (path) { + var lang = 'ENG'; + if(path.basename.indexOf('-') >= 0){ + lang = path.basename.replace('README-', '').toUpperCase(); + } + path.basename = TITLE + ' ('+lang+')'; + path.extname = '.pdf'; + })) + .pipe(gulp.dest('./build/')); + }); + + } + + return genericTask; + +}; \ No newline at end of file diff --git a/gulp/tasks/clean.js b/gulp/tasks/clean.js new file mode 100644 index 0000000..7c9b14c --- /dev/null +++ b/gulp/tasks/clean.js @@ -0,0 +1,7 @@ +module.exports = function (gulp, rimraf) { + + gulp.task('clean', function() { + return gulp.src('./temp/', { read: false }).pipe(rimraf()); + }); + +}; \ No newline at end of file diff --git a/gulp/tasks/copy-images.js b/gulp/tasks/copy-images.js new file mode 100644 index 0000000..113599d --- /dev/null +++ b/gulp/tasks/copy-images.js @@ -0,0 +1,7 @@ +module.exports = function (gulp) { + + gulp.task('copy:images', function(){ + return gulp.src(['images/*.svg','meta.json']).pipe(gulp.dest('./temp')); + }); + +}; diff --git a/gulp/tasks/copy-md.js b/gulp/tasks/copy-md.js new file mode 100644 index 0000000..a87a0c5 --- /dev/null +++ b/gulp/tasks/copy-md.js @@ -0,0 +1,15 @@ +module.exports = function (gulp, replace) { + + gulp.task('copy:md', function(){ + return gulp.src(['README.md', 'i18n/README-*.md']) + // @todo I have no idea where should the TOC go?! + // for now, let's keep the TOC content and remove these markers + .pipe(replace('', '')) + .pipe(replace('', '')) + + // preapre the image paths for the renderer + .pipe(replace(/https:\/\/rawgit.com\/mgechev\/angularjs-in-patterns\/master\/images/g, '.')) + .pipe(gulp.dest('./temp/')); + }); + +}; \ No newline at end of file diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js new file mode 100644 index 0000000..4480df4 --- /dev/null +++ b/gulp/tasks/default.js @@ -0,0 +1,5 @@ +module.exports = function (gulp, runSequence) { + gulp.task('default', function(cb){ + runSequence('clean', ['copy:images', 'copy:md'], 'doc:pdf:all', cb); + }); +}; \ No newline at end of file diff --git a/gulp/tasks/i18n.js b/gulp/tasks/i18n.js new file mode 100644 index 0000000..1938476 --- /dev/null +++ b/gulp/tasks/i18n.js @@ -0,0 +1,13 @@ +module.exports = function (gulp, glob, genericTask, runSequence) { + + // build custom tasks for i18n + + glob.sync('./temp/README-*.md').map(function(file){ + return file.replace(/.*README\-|\.md$/g, ''); + }).concat(['all', 'eng']).forEach(function(lang){ + genericTask(lang); + gulp.task('doc:pdf:'+lang, function(cb){ + runSequence('clean', ['copy:images', 'copy:md'], 'generate:pdf:'+lang, cb); + }); + }); +}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 0bc3bbd..94005fd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,83 +1,15 @@ -var gulp = require('gulp'); -var markdownpdf = require('gulp-markdown-pdf'); -var path = require('path'); -var replace = require('gulp-replace'); -var package = require('./package.json'); -var rename = require('gulp-rename'); -var rimraf = require('gulp-rimraf'); -var runSequence = require('run-sequence'); -var glob = require('glob'); - -var TITLE = 'AngularJS in Patterns'; - -function genericTask(lang){ - - gulp.task('generate:pdf:' + lang, function() { - - var files = ['./temp/*.md']; - if (lang === 'eng'){ - files = './temp/README.md'; - } - else if(lang !== 'all'){ - files = ['./temp/*-'+lang+'.md']; - } - - return gulp.src(files) - .pipe(markdownpdf({ - cwd: path.resolve('./temp/'), - layout: 'github' - })) - .on('error', function(err){ - gutil.log(gutil.colors.red('doc task failed'), err); - }) - .pipe(rename(function (path) { - var lang = 'ENG'; - if(path.basename.indexOf('-') >= 0){ - lang = path.basename.replace('README-', '').toUpperCase(); - } - path.basename = TITLE + ' ('+lang+')'; - path.extname = '.pdf' - })) - .pipe(gulp.dest('./build/')); - }); - -} - -// build custom tasks for i18n - -glob.sync('./temp/README-*.md').map(function(file){ - - return file.replace('README-', ''); - -}).concat(['all', 'eng']).forEach(function(lang){ - - genericTask(lang); - gulp.task('doc:pdf:'+lang, function(cb){ - runSequence('clean', ['copy:images', 'copy:md'], 'generate:pdf:'+lang, cb); - }); - -}); - -gulp.task('default', function(cb){ - runSequence('clean', ['copy:images', 'copy:md'], 'doc:pdf:all', cb); -}); - -gulp.task('copy:md', function(){ - return gulp.src(['README.md', 'i18n/README-*.md']) - // @todo I have no idea where should the TOC go?! - // for now, let's keep the TOC content and remove these markers - .pipe(replace('', '')) - .pipe(replace('', '')) - - // preapre the image paths for the renderer - .pipe(replace(/https:\/\/rawgit.com\/mgechev\/angularjs-in-patterns\/master\/images/g, '.')) - .pipe(gulp.dest('./temp/')); -}); - -gulp.task('copy:images', function(){ - return gulp.src(['images/*.svg','meta.json']).pipe(gulp.dest('./temp')); -}); - -gulp.task('clean', function() { - return gulp.src('./temp/', { read: false }).pipe(rimraf()); -}); +var GulpDI = require('gulp-di'); +var gulp = require('gulp'); +var di = GulpDI(require('gulp'), { + pattern : ['gulp-*', 'gulp.*', 'run-sequence', 'glob'], + rename : { + 'gulp-markdown-pdf' : 'markdownpdf' + } +}) +.provide({ + TITLE : 'AngularJS in Patterns', + path : require('path') +}) +.modules('./gulp/modules') +.tasks('./gulp/tasks') +.resolve(); \ No newline at end of file diff --git a/package.json b/package.json index 400ea87..ef0157f 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ "url": "git://github.com/mgechev/angularjs-patterns.git" }, "author": "mgechev", - "contributors": [{ - "name" : "Wassim Chegham", - "email" : "maneki.nekko@gmail.com", - "url" : "https://github.com/manekinekko/" - }], + "contributors": [ + { + "name": "Wassim Chegham", + "email": "maneki.nekko@gmail.com", + "url": "https://github.com/manekinekko/" + } + ], "license": "MIT", "gitHead": "2009434a6dfdbe5f06f81253fb853840af753b36", "readmeFilename": "README.md", @@ -28,5 +30,8 @@ "gulp-rimraf": "^0.1.1", "markdown-styles": "https://github.com/mgechev/markdown-styles/tarball/master", "run-sequence": "^1.1.1" + }, + "dependencies": { + "gulp-di": "0.0.1" } } From deda1da2a72d71a2c391d72444cf1ad5f6e53f84 Mon Sep 17 00:00:00 2001 From: mgechev Date: Mon, 9 Jan 2017 15:40:34 +0200 Subject: [PATCH 27/29] docs: change angularjs to angular 1 --- README.md | 170 +++++++++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 3eb7653..ddf20cc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AngularJS in Patterns +# Angular 1 in Patterns @@ -7,14 +7,14 @@ * [Translations](#translations) * [Abstract](#abstract) * [Introduction](#introduction) -* [AngularJS overview](#angularjs-overview) +* [Angular 1 overview](#angular-1-overview) * [Partials](#partials) * [Controllers](#controllers) * [Scope](#scope) * [Directives](#directives) * [Filters](#filters) * [Services](#services) -* [AngularJS Patterns](#angularjs-patterns) +* [Angular 1 Patterns](#angular-1-patterns) * [Services](#services-1) * [Singleton](#singleton) * [Factory Method](#factory-method) @@ -43,29 +43,29 @@ ## Translations -- [Japanese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ja-jp.md) by [morizotter](https://twitter.com/morizotter) +- [Japanese Translation](https://github.com/mgechev/angular-in-patterns/blob/master/i18n/README-ja-jp.md) by [morizotter](https://twitter.com/morizotter) - [Russian Translation 1](http://habrahabr.ru/post/250149/) -- [Russian Translation 2](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-ru-ru.md) by [l.s.kramarov](https://github.com/lskramarov) -- [French Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-fr-fr.md) by [manekinekko](https://github.com/manekinekko) -- [Chinese Translation](https://github.com/mgechev/angularjs-in-patterns/blob/master/i18n/README-zh-cn.md) by [carlosliu](https://github.com/carlosliu) +- [Russian Translation 2](https://github.com/mgechev/angular-in-patterns/blob/master/i18n/README-ru-ru.md) by [l.s.kramarov](https://github.com/lskramarov) +- [French Translation](https://github.com/mgechev/angular-in-patterns/blob/master/i18n/README-fr-fr.md) by [manekinekko](https://github.com/manekinekko) +- [Chinese Translation](https://github.com/mgechev/angular-in-patterns/blob/master/i18n/README-zh-cn.md) by [carlosliu](https://github.com/carlosliu) ## Abstract One of the best ways to learn something new is to see how the things you already know are used in it. This document does not intend to make its readers familiar with the design or architectural patterns; it suggests basic understanding of the concepts of the OOP, design patterns and architectural patterns. -The goal of this paper is to describe how different software design and architectural patterns are applied in AngularJS or any AngularJS single-page application. +The goal of this paper is to describe how different software design and architectural patterns are applied in Angular 1 or any Angular 1 single-page application. ## Introduction -The document begins with brief overview of the AngularJS framework. The overview explains the main AngularJS components - directives, filters, controllers, services, scope. The second section lists and describes different design and architectural patterns, which are implemented inside the framework. The patterns are grouped by the AngularJS component they are used in. If some patterns are used inside multiple components it will be explicitly mentioned. +The document begins with brief overview of the Angular 1 framework. The overview explains the main Angular 1 components - directives, filters, controllers, services, scope. The second section lists and describes different design and architectural patterns, which are implemented inside the framework. The patterns are grouped by the Angular 1 component they are used in. If some patterns are used inside multiple components it will be explicitly mentioned. -The last section contains a few architectural patterns, which are commonly used inside most of the single-page applications built with AngularJS. +The last section contains a few architectural patterns, which are commonly used inside most of the single-page applications built with Angular 1. -## AngularJS overview +## Angular 1 overview -AngularJS is a JavaScript framework developed by Google. It intends to provide a solid base for the development of CRUD Single-Page Applications (SPA). +Angular 1 is a JavaScript framework developed by Google. It intends to provide a solid base for the development of CRUD Single-Page Applications (SPA). SPA is a web application, which once loaded, does not require full page reload when the user performs any actions with it. This means that all application resources (data, templates, scripts, styles) should be loaded with the initial request or better - the information and resources should be loaded on demand. -Since most of the CRUD applications has common characteristics and requirements, AngularJS intends to provide the optimal set of them out-of-the-box. A few important features of AngularJS are: +Since most of the CRUD applications has common characteristics and requirements, Angular 1 intends to provide the optimal set of them out-of-the-box. A few important features of Angular 1 are: - two-way data binding - dependency injection @@ -73,7 +73,7 @@ Since most of the CRUD applications has common characteristics and requirements, - testability - abstraction -The separation of concerns is achieved by dividing each AngularJS application into separate components, such as: +The separation of concerns is achieved by dividing each Angular 1 application into separate components, such as: - partials - controllers @@ -85,9 +85,9 @@ These components can be grouped inside different modules, which helps to achieve ### Partials -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). +The partials are HTML strings. They may contain Angular 1 expressions inside the elements or their attributes. One of the distinctions between Angular 1 and the others frameworks is the fact that Angular 1' 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. +Initially each SPA loads `index.html` file. In the case of Angular 1 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. **Sample partial** @@ -105,11 +105,11 @@ Initially each SPA loads `index.html` file. In the case of AngularJS this file c ``` -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. +With Angular 1 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. ### Controllers -The AngularJS controllers are JavaScript functions, which help handling the user interactions with the web application (for example mouse events, keyboard events, etc.), by attaching methods to the *scope*. All required external, for the controllers, components are provided through the Dependency Injection mechanism of AngularJS. The controllers are also responsible for providing the *model* to the partials by attaching data to the *scope*. We can think of this data as *view model*. +The Angular 1 controllers are JavaScript functions, which help handling the user interactions with the web application (for example mouse events, keyboard events, etc.), by attaching methods to the *scope*. All required external, for the controllers, components are provided through the Dependency Injection mechanism of Angular 1. The controllers are also responsible for providing the *model* to the partials by attaching data to the *scope*. We can think of this data as *view model*. ```JavaScript function MyController($scope) { @@ -128,13 +128,13 @@ For example, if we wire the sample controller above with the partial provided in 1. 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. 2. 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. +All the custom elements, attributes, comments or classes could be recognized as Angular 1 *directives* if they are previously defined as ones. ### Scope -In AngularJS scope is a JavaScript object, which is exposed to the partials. The scope could contain different properties - primitives, objects or methods. All methods attached to the scope could be invoked by evaluation of AngularJS expression inside the partials associated with the given scope or direct call of the method by any component, which keeps reference to the scope. By using appropriate *directives*, the data attached to the scope could be binded to the view in such a way that each change in the partial will reflect a scope property and each change of a scope property will reflect the partial. +In Angular 1 scope is a JavaScript object, which is exposed to the partials. The scope could contain different properties - primitives, objects or methods. All methods attached to the scope could be invoked by evaluation of Angular 1 expression inside the partials associated with the given scope or direct call of the method by any component, which keeps reference to the scope. By using appropriate *directives*, the data attached to the scope could be binded to the view in such a way that each change in the partial will reflect a scope property and each change of a scope property will reflect the partial. -Another important characteristics 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. +Another important characteristics of the scopes of any Angular 1 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: @@ -165,7 +165,7 @@ With `div#child` is associated `ChildCtrl` but since the scope injected inside ` ### Directives -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 directive or consider refactoring of already existing one, which could handle the required DOM manipulations. +In Angular 1 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 directive 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: - template @@ -202,11 +202,11 @@ myModule.directive('alertButton', function () { In the example above the tag `` will be replaced button element. When the user clicks on the button the string `42` will be alerted. -Since the intent of this paper is not to explain the complete API of AngularJS, we will stop with the directives here. +Since the intent of this paper is not to explain the complete API of Angular 1, we will stop with the directives here. ### Filters -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. +The filters in Angular 1 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 a definition of a sample filter, which changes the given string to uppercase: @@ -257,11 +257,11 @@ function MyCtrl(Developer) { } ``` -## AngularJS Patterns +## Angular 1 Patterns -In the next a couple of sections, we are going to take a look how the traditional design and architectural patterns are composed in the AngularJS components. +In the next a couple of sections, we are going to take a look how the traditional design and architectural patterns are composed in the Angular 1 components. -In the last chapter we are going to take a look at some architectural patterns, which are frequently used in the development of Single-Page Applications with (but not limited to) AngularJS. +In the last chapter we are going to take a look at some architectural patterns, which are frequently used in the development of Single-Page Applications with (but not limited to) Angular 1. ### Services @@ -271,18 +271,18 @@ In the last chapter we are going to take a look at some architectural patterns, In the UML diagram bellow is illustrated the singleton design pattern. -![Singleton](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/singleton.svg "Fig. 1") +![Singleton](https://rawgit.com/mgechev/angular-in-patterns/master/images/singleton.svg "Fig. 1") -When given dependency is required by any component, AngularJS resolves it using the following algorithm: +When given dependency is required by any component, Angular 1 resolves it using the following algorithm: - Takes its name and makes a lookup at a hash map, which is defined into a lexical closure (so it has a private visibility). -- If the dependency exists AngularJS pass it as parameter to the component, which requires it. +- If the dependency exists Angular 1 pass it as parameter to the component, which requires it. - If the dependency does not exists: - - AngularJS instantiate it by calling the factory method of its provider (i.e. `$get`). Note that instantiating the dependency may require recursive call to the same algorithm, for resolving all the dependencies required by the given dependency. This process may lead to circular dependency. - - AngularJS caches it inside the hash map mentioned above. - - AngularJS passes it as parameter to the component, which requires it. + - Angular 1 instantiate it by calling the factory method of its provider (i.e. `$get`). Note that instantiating the dependency may require recursive call to the same algorithm, for resolving all the dependencies required by the given dependency. This process may lead to circular dependency. + - Angular 1 caches it inside the hash map mentioned above. + - Angular 1 passes it as parameter to the component, which requires it. -We can take better look at the AngularJS' source code, which implements the method `getService`: +We can take better look at the Angular 1' source code, which implements the method `getService`: ```JavaScript function getService(serviceName) { @@ -321,7 +321,7 @@ For further discussion on this topic Misko Hevery's [article](http://googletesti >The factory method pattern is a creational pattern, which uses factory methods to deal with the problem of creating objects without specifying the exact class of object that will be created. This is done by creating objects via a factory method, which is either specified in an interface (abstract class) and implemented in implementing classes (concrete classes); or implemented in a base class, which can be overridden when inherited in derived classes; rather than by a constructor. -![Factory Method](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/factory-method.svg "Fig. 2") +![Factory Method](https://rawgit.com/mgechev/angular-in-patterns/master/images/factory-method.svg "Fig. 2") Lets consider the following snippet: @@ -347,7 +347,7 @@ In the code above we use the `config` callback in order to define new "provider" Each service, filter, directive and controller has a provider (i.e. object which factory method, called `$get`), which is responsible for creating the component's instance. -We can dig a little bit deeper in AngularJS' implementation: +We can dig a little bit deeper in Angular 1' implementation: ```JavaScript //... @@ -397,7 +397,7 @@ From the example above we can notice how the `$get` method is actually used: instanceInjector.invoke(provider.$get, provider, undefined, servicename) ``` -The snippet above calls the `invoke` method of `instanceInjector` with the factory method (i.e. `$get`) of given service, as first argument. Inside `invoke`'s body `annotate` is called with first argument the factory method. Annotate resolves all dependencies through the dependency injection mechanism of AngularJS, which was considered above. When all dependencies are resolved the factory method is being called: `fn.apply(self, args)`. +The snippet above calls the `invoke` method of `instanceInjector` with the factory method (i.e. `$get`) of given service, as first argument. Inside `invoke`'s body `annotate` is called with first argument the factory method. Annotate resolves all dependencies through the dependency injection mechanism of Angular 1, which was considered above. When all dependencies are resolved the factory method is being called: `fn.apply(self, args)`. If we think in terms of the UML diagram above we can call the provider a "ConcreteCreator" and the actual component, which is being created a "Product". @@ -411,9 +411,9 @@ There are a few benefits of using the factory method pattern in this case, becau >The decorator pattern (also known as Wrapper, an alternative naming shared with the Adapter pattern) is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. -![Decorator](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/decorator.svg "Fig. 4") +![Decorator](https://rawgit.com/mgechev/angular-in-patterns/master/images/decorator.svg "Fig. 4") -AngularJS provides out-of-the-box way for extending and/or enhancing the functionality of already existing services. Using the method `decorator` of `$provide` you can create "wrapper" of any service you have previously defined or used by a third-party: +Angular 1 provides out-of-the-box way for extending and/or enhancing the functionality of already existing services. Using the method `decorator` of `$provide` you can create "wrapper" of any service you have previously defined or used by a third-party: ```JavaScript myModule.controller('MainCtrl', function (foo) { @@ -442,10 +442,10 @@ myModule.config(function ($provide) { }); }); ``` -The example above defines new service called `foo`. In the `config` callback is called the method `$provide.decorator` with first argument `"foo"`, which is the name of the service, we want to decorate and second argument factory function, which implements the actual decoration. `$delegate` keeps reference to the original service `foo`. Using the dependency injection mechanism of AngularJS, reference to this local dependency is passed as first argument of the constructor function. +The example above defines new service called `foo`. In the `config` callback is called the method `$provide.decorator` with first argument `"foo"`, which is the name of the service, we want to decorate and second argument factory function, which implements the actual decoration. `$delegate` keeps reference to the original service `foo`. Using the dependency injection mechanism of Angular 1, reference to this local dependency is passed as first argument of the constructor function. We decorate the service by overriding its method `bar`. The actual decoration is simply extending `bar` by invoking one more `console.log statement` - `console.log('Decorated');` and after that call the original `bar` method with the appropriate context. -Using this pattern is especially useful when we need to modify the functionality of third party services. In cases when multiple similar decorations are required (like performance measurement of multiple methods, authorization, logging, etc.), we may have a lot of duplications and violate the DRY principle. In such cases it is useful to use [aspect-oriented programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming). The only AOP framework for AngularJS I'm aware of could be found at [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop). +Using this pattern is especially useful when we need to modify the functionality of third party services. In cases when multiple similar decorations are required (like performance measurement of multiple methods, authorization, logging, etc.), we may have a lot of duplications and violate the DRY principle. In such cases it is useful to use [aspect-oriented programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming). The only AOP framework for Angular 1 I'm aware of could be found at [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop). #### Facade @@ -459,9 +459,9 @@ Using this pattern is especially useful when we need to modify the functionality >4. wrap a poorly designed collection of APIs with a single well-designed API (as per task needs). -![Facade](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/facade.svg "Fig. 11") +![Facade](https://rawgit.com/mgechev/angular-in-patterns/master/images/facade.svg "Fig. 11") -There are a few facades in AngularJS. Each time you want to provide higher level API to given functionality you practically create a facade. +There are a few facades in Angular 1. Each time you want to provide higher level API to given functionality you practically create a facade. For example, lets take a look how we can create an `XMLHttpRequest` POST request: @@ -482,7 +482,7 @@ http.onreadystatechange = function () { } http.send(params); ``` -But if we want to post this data using the AngularJS' `$http` service we can: +But if we want to post this data using the Angular 1' `$http` service we can: ```JavaScript $http({ @@ -510,7 +510,7 @@ Even higher level of abstraction is being created by `$resource`, which is build >A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. -![Proxy](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/proxy.svg "Fig. 9") +![Proxy](https://rawgit.com/mgechev/angular-in-patterns/master/images/proxy.svg "Fig. 9") We can distinguish three different types of proxy: @@ -518,7 +518,7 @@ We can distinguish three different types of proxy: - Remote Proxy - Protection Proxy -In this sub-chapter we are going to take a look at AngularJS' implementation of Virtual Proxy. +In this sub-chapter we are going to take a look at Angular 1' implementation of Virtual Proxy. In the snippet bellow, there is a call to the `get` method of `$resource` instance, called `User`: @@ -530,7 +530,7 @@ console.log(user); //{} `console.log` would outputs an empty object. Since the AJAX request, which happens behind the scene, when `User.get` is invoked, is asynchronous, we don't have the actual user when `console.log` is called. Just after `User.get` makes the GET request it returns an empty object and keeps reference to it. We can think of this object as virtual proxy (a simple placeholder), which would be populated with the actual data once the client receives response by the server. -How does this works with AngularJS? Well, lets consider the following snippet: +How does this works with Angular 1? Well, lets consider the following snippet: ```JavaScript function MainCtrl($scope, $resource) { @@ -542,17 +542,17 @@ function MainCtrl($scope, $resource) { ```html ``` -Initially when the snippet above executes, the property `user` of the `$scope` object will be with value an empty object (`{}`), which means that `user.name` will be undefined and nothing will be rendered. Internally AngularJS will keep reference to this empty object. Once the server returns response for the get request, AngularJS will populate the object with the data, received from the server. During the next `$digest` loop AngularJS will detect change in `$scope.user`, which will lead to update of the view. +Initially when the snippet above executes, the property `user` of the `$scope` object will be with value an empty object (`{}`), which means that `user.name` will be undefined and nothing will be rendered. Internally Angular 1 will keep reference to this empty object. Once the server returns response for the get request, Angular 1 will populate the object with the data, received from the server. During the next `$digest` loop Angular 1 will detect change in `$scope.user`, which will lead to update of the view. #### Active Record >The Active Record object is an object, which carries both data and behavior. Usually most of the data in these objects is persistent, responsibility of the Active Record object is to take care of the communication with the database in order to create, update, retrieve or delete the data. It may delegate this responsibility to lower level objects but calls to instance or static methods of the active record object cause the database communication. -![Active Record](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/active-record.svg "Fig. 7") +![Active Record](https://rawgit.com/mgechev/angular-in-patterns/master/images/active-record.svg "Fig. 7") -AngularJS defines a service called `$resource`. In the current version of AngularJS (1.2+) it is being distributed in module outside of the AngularJS' core. +Angular 1 defines a service called `$resource`. In the current version of Angular 1 (1.2+) it is being distributed in module outside of the Angular 1' core. -According to the AngularJS' documentation `$resource` is: +According to the Angular 1' documentation `$resource` is: >A factory which creates a resource object that lets you interact with RESTful server-side data sources. >The returned resource object has action methods which provide high-level behaviors without the need to interact with the low level $http service. @@ -577,9 +577,9 @@ This way we can use the constructor function and its static methods by: User.get({ userid: userid }); ``` -The code above will immediately return an empty object and keep reference to it. Once the response have been successfully returned and parsed, AngularJS will populate this object with the received data (see [proxy](#proxy)). +The code above will immediately return an empty object and keep reference to it. Once the response have been successfully returned and parsed, Angular 1 will populate this object with the received data (see [proxy](#proxy)). -You can find more details for `$resource` [The magic of $resource](http://blog.mgechev.com/2014/02/05/angularjs-resource-active-record-http/) and [AngularJS' documentation](https://docs.angularjs.org/api/ngResource/service/$resource). +You can find more details for `$resource` [The magic of $resource](http://blog.mgechev.com/2014/02/05/angularjs-resource-active-record-http/) and [Angular 1' documentation](https://docs.angularjs.org/api/ngResource/service/$resource). Since Martin Fowler states that @@ -591,11 +591,11 @@ Since Martin Fowler states that >Create a chain of composable filters to implement common pre-processing and post-processing tasks during a Web page request. -![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/intercepting-filters.svg "Fig. 3") +![Composite](https://rawgit.com/mgechev/angular-in-patterns/master/images/intercepting-filters.svg "Fig. 3") In some cases you need to do some kind of pre and/or post processing of HTTP requests. In the case of the Intercepting Filters you pre/post process given HTTP request/response in order to include logging, security or any other concern, which is influenced by the request body or headers. Basically the Intercepting Filters pattern include a chain of filters, each of which process data in given order. The output of each filter is input of the next one. -In AngularJS we have the idea of the Intercepting Filters in `$httpProvider`. `$httpProvider` has an array property called `interceptors`, which contains a list of objects. Each object may have properties called: `request`, `response`, `requestError`, `responseError`. +In Angular 1 we have the idea of the Intercepting Filters in `$httpProvider`. `$httpProvider` has an array property called `interceptors`, which contains a list of objects. Each object may have properties called: `request`, `response`, `requestError`, `responseError`. `requestError` is an interceptor, which gets called when a previous interceptor threw an error or resolved with a rejection, respectively `responseError` is being called when the previous `response` interceptor has thrown an error. @@ -620,7 +620,7 @@ $httpProvider.interceptors.push(function($q, dependency1, dependency2) { >The composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects are to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. -![Composite](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/composite.svg "Fig. 3") +![Composite](https://rawgit.com/mgechev/angular-in-patterns/master/images/composite.svg "Fig. 3") According to the Gang of Four, MVC is nothing more than combination of: @@ -628,7 +628,7 @@ According to the Gang of Four, MVC is nothing more than combination of: - Composite - Observer -They state that the view is composition of components. In AngularJS the situation is similar. Our views are formed by a composition of directives and DOM elements, on which these directives could be applied. +They state that the view is composition of components. In Angular 1 the situation is similar. Our views are formed by a composition of directives and DOM elements, on which these directives could be applied. Lets look at the following example: @@ -669,10 +669,10 @@ In the second, JavaScript, example we see that the `template` property of the di >In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence. -![Interpreter](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/interpreter.svg "Fig. 6") +![Interpreter](https://rawgit.com/mgechev/angular-in-patterns/master/images/interpreter.svg "Fig. 6") -Behind its `$parse` service, AngularJS provides its own implementation of interpreter of a DSL (Domain Specific Language). The used DSL is simplified and modified version of JavaScript. -The main differences between the JavaScript expressions and AngularJS expressions that AngularJS expressions: +Behind its `$parse` service, Angular 1 provides its own implementation of interpreter of a DSL (Domain Specific Language). The used DSL is simplified and modified version of JavaScript. +The main differences between the JavaScript expressions and Angular 1 expressions that Angular 1 expressions: - may contain filters with UNIX like pipe syntax - don't throw any errors @@ -690,7 +690,7 @@ var Parser; Once given expression have been tokenized it is cached internally, because of performance concerns. -The terminal expressions in the AngularJS DSL are defined as follows: +The terminal expressions in the Angular 1 DSL are defined as follows: ```JavaScript var OPERATORS = { @@ -725,9 +725,9 @@ var OPERATORS = { We can think of the function associated with each terminal as implementation of the `AbstractExpression`'s interface. -Each `Client` interprets given AngularJS expression in a specific context - specific scope. +Each `Client` interprets given Angular 1 expression in a specific context - specific scope. -Few sample AngularJS expressions are: +Few sample Angular 1 expressions are: ```JavaScript // toUpperCase filter is applied to the result of the expression @@ -739,7 +739,7 @@ Few sample AngularJS expressions are: > Renders information into HTML by embedding markers in an HTML page. -![Template View](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/template-view.svg "Fig. 8") +![Template View](https://rawgit.com/mgechev/angular-in-patterns/master/images/template-view.svg "Fig. 8") The dynamic page rendering is not that trivial thing. It is connected with a lot of string concatenations, manipulations and frustration. Far easier way to build your dynamic page is to write your markup and embed little expressions inside it, which are lately evaluated in given context and so the whole template is being compiled to its end format. In our case this format is going to be HTML (or even DOM). This is exactly what the template engines do - they take given DSL, evaluate it in the appropriate context and then turn it into its end format. @@ -770,8 +770,8 @@ For example if we evaluate the template above in the context of the following ob baz ``` -AngularJS templates are actually HTML, they are not in an intermediate format like the traditional templates are. -What AngularJS compiler does is to traverse the DOM tree and look for already known directives (elements, attributes, classes or even comments). When AngularJS finds any of these directives it invokes the logic associated with them, which may involve evaluation of different expressions in the context of the current scope. +Angular 1 templates are actually HTML, they are not in an intermediate format like the traditional templates are. +What Angular 1 compiler does is to traverse the DOM tree and look for already known directives (elements, attributes, classes or even comments). When Angular 1 finds any of these directives it invokes the logic associated with them, which may involve evaluation of different expressions in the context of the current scope. For example: @@ -796,11 +796,11 @@ will produce the same result as the one above. The main difference here is that >The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems. -![Observer](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/observer.svg "Fig. 7") +![Observer](https://rawgit.com/mgechev/angular-in-patterns/master/images/observer.svg "Fig. 7") -There are two basic ways of communication between the scopes in an AngularJS application. The first one is calling methods of parent scope by a child scope. This is possible since the child scope inherits prototypically by its parent, as mentioned above (see [Scope](#scope)). This allows communication in a single direction - child to parent. Some times it is necessary to call method of given child scope or notify it about a triggered event in the context of the parent scope. AngularJS provides built-in observer pattern, which allows this. Another possible use case, of the observer pattern, is when multiple scopes are interested in given event but the scope, in which context the event is triggered, is not aware of them. This allows decoupling between the different scopes, non of the scopes should be aware of the rest of the scopes. +There are two basic ways of communication between the scopes in an Angular 1 application. The first one is calling methods of parent scope by a child scope. This is possible since the child scope inherits prototypically by its parent, as mentioned above (see [Scope](#scope)). This allows communication in a single direction - child to parent. Some times it is necessary to call method of given child scope or notify it about a triggered event in the context of the parent scope. Angular 1 provides built-in observer pattern, which allows this. Another possible use case, of the observer pattern, is when multiple scopes are interested in given event but the scope, in which context the event is triggered, is not aware of them. This allows decoupling between the different scopes, non of the scopes should be aware of the rest of the scopes. -Each AngularJS scope has public methods called `$on`, `$emit` and `$broadcast`. The method `$on` accepts topic as first argument and callback as second. We can think of the callback as an observer - an object, which implements the `Observer` interface (in JavaScript the functions are first-class, so we can provide only implementation of the `notify` method): +Each Angular 1 scope has public methods called `$on`, `$emit` and `$broadcast`. The method `$on` accepts topic as first argument and callback as second. We can think of the callback as an observer - an object, which implements the `Observer` interface (in JavaScript the functions are first-class, so we can provide only implementation of the `notify` method): ```JavaScript function ExampleCtrl($scope) { @@ -834,9 +834,9 @@ For a best practice example see [Observer Pattern as an External Service](#obser >The chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain. -![Chain of Responsibilities](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") +![Chain of Responsibilities](https://rawgit.com/mgechev/angular-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") -As stated above the scopes in an AngularJS application form a hierarchy known as the scope chain. Some of the scopes are "isolated", which means that they don't inherit prototypically by their parent scope, but are connected to it via their `$parent` property. +As stated above the scopes in an Angular 1 application form a hierarchy known as the scope chain. Some of the scopes are "isolated", which means that they don't inherit prototypically by their parent scope, but are connected to it via their `$parent` property. When `$emit` or `$broadcast` are called we can think of the scope chain as event bus, or even more accurately chain of responsibilities. Once the event is triggered it is emitted downwards or upwards (depending on the method, which was called). Each subsequent scope may: @@ -871,9 +871,9 @@ The different handlers from the UML diagram above are the different scopes, inje >In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters. -![Command](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/command.svg "Fig. 11") +![Command](https://rawgit.com/mgechev/angular-in-patterns/master/images/command.svg "Fig. 11") -Before continuing with the application of the command pattern lets describe how AngularJS implements data binding. +Before continuing with the application of the command pattern lets describe how Angular 1 implements data binding. When we want to bind our model to the view we use the directives `ng-bind` (for single-way data binding) and `ng-model` (for two-way data binding). For example, if we want each change in the model `foo` to reflect the view we can: @@ -881,7 +881,7 @@ When we want to bind our model to the view we use the directives `ng-bind` (for ``` -Now each time we change the value of `foo` the inner text of the span will be changed. We can achieve the same effect with more complex AngularJS expressions, like: +Now each time we change the value of `foo` the inner text of the span will be changed. We can achieve the same effect with more complex Angular 1 expressions, like: ```html @@ -889,7 +889,7 @@ Now each time we change the value of `foo` the inner text of the span will be ch In the example above the value of the span will be the concatenated uppercased value of `foo` and `bar`. What happens behind the scene? -Each `$scope` has method called `$watch`. When the AngularJS compiler find the directive `ng-bind` it creates a new watcher of the expression `foo + ' ' + bar | uppercase`, i.e. `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`. The callback will be triggered each time the value of the expression change. In the current case the callback will update the value of the span. +Each `$scope` has method called `$watch`. When the Angular 1 compiler find the directive `ng-bind` it creates a new watcher of the expression `foo + ' ' + bar | uppercase`, i.e. `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`. The callback will be triggered each time the value of the expression change. In the current case the callback will update the value of the span. Here are the first a couple of lines of the implementation of `$watch`: @@ -908,7 +908,7 @@ $watch: function(watchExp, listener, objectEquality) { //... ``` -We can think of the `watcher` object as a command. The expression of the command is being evaluated on each [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) loop. Once AngularJS detects change in the expression, it invokes the `listener` function. The `watcher` command encapsulates the whole information required for watching given expression and delegates the execution of the command to the `listener` (the actual receiver). We can think of the `$scope` as the command's `Client` and the `$digest` loop as the command's `Invoker`. +We can think of the `watcher` object as a command. The expression of the command is being evaluated on each [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) loop. Once Angular 1 detects change in the expression, it invokes the `listener` function. The `watcher` command encapsulates the whole information required for watching given expression and delegates the execution of the command to the `listener` (the actual receiver). We can think of the `$scope` as the command's `Client` and the `$digest` loop as the command's `Invoker`. ### Controllers @@ -916,17 +916,17 @@ We can think of the `watcher` object as a command. The expression of the command >An object that handles a request for a specific page or action on a Web site. Martin Fowler -![Page Controller](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/page-controller.svg "Fig. 8") +![Page Controller](https://rawgit.com/mgechev/angular-in-patterns/master/images/page-controller.svg "Fig. 8") According to [4](#references) the page controller: >Page Controller pattern accept input from the page request, invoke the requested actions on the model, and determine the correct view to use for the resulting page. Separate the dispatching logic from any view-related code -Since there is a lot of duplicate behavior between the different pages (like rendering footers, headers, taking care of the user's session, etc.) page controllers can form a hierarchy. In AngularJS we have controllers, which are with more limited scope of responsibilities. They don't accept user requests, since this is responsibility of the `$route` or `$state` services and the page rendering is responsibility of the directives `ng-view`/`ui-view`. +Since there is a lot of duplicate behavior between the different pages (like rendering footers, headers, taking care of the user's session, etc.) page controllers can form a hierarchy. In Angular 1 we have controllers, which are with more limited scope of responsibilities. They don't accept user requests, since this is responsibility of the `$route` or `$state` services and the page rendering is responsibility of the directives `ng-view`/`ui-view`. -Similarly to the page controllers, AngularJS controllers handle user interactions, provide and update the models. The model is exposed to the view when it is being attached to the scope, all methods invoked by the view, in result of user actions, are ones, which are already attached to the scope. Another similarity between the page controllers and the AngularJS controllers is the hierarchy, which they form. It corresponds to the scope hierarchy. That way common actions can be isolated to the base controllers. +Similarly to the page controllers, Angular 1 controllers handle user interactions, provide and update the models. The model is exposed to the view when it is being attached to the scope, all methods invoked by the view, in result of user actions, are ones, which are already attached to the scope. Another similarity between the page controllers and the Angular 1 controllers is the hierarchy, which they form. It corresponds to the scope hierarchy. That way common actions can be isolated to the base controllers. -The controllers in AngularJS are quite similar to the code-behind in ASP.NET WebForms, since their responsibilities almost overlap. +The controllers in Angular 1 are quite similar to the code-behind in ASP.NET WebForms, since their responsibilities almost overlap. Here is an example hierarchy between few controllers: ```HTML @@ -995,7 +995,7 @@ In the example above we have IIFE (Immediately-Invoked Function Expression), whi In this case the user of the `Page` object doesn't has direct access to the `title` variable, which is defined inside the local scope of the IIFE. -The module pattern is very useful when defining services in AngularJS. Using this pattern we can simulate (and actually achieve) privacy: +The module pattern is very useful when defining services in Angular 1. Using this pattern we can simulate (and actually achieve) privacy: ```javascript app.factory('foo', function () { @@ -1022,9 +1022,9 @@ Once we want to inject `foo` inside any other component we won't be able to use >A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in memory data representation (the domain layer). The goal of the pattern is to keep the in memory representation and the persistent data store independent of each other and the data mapper itself. -![Data Mapper](https://rawgit.com/mgechev/angularjs-in-patterns/master/images/data-mapper.svg "Fig. 10") +![Data Mapper](https://rawgit.com/mgechev/angular-in-patterns/master/images/data-mapper.svg "Fig. 10") -As the description above states, the data mapper is used for bidirectional transfer of data between a persistent data store and an in memory data representation. Usually our AngularJS application communicates with API server, which is written in any server-side language (Ruby, PHP, Java, JavaScript, etc.). +As the description above states, the data mapper is used for bidirectional transfer of data between a persistent data store and an in memory data representation. Usually our Angular 1 application communicates with API server, which is written in any server-side language (Ruby, PHP, Java, JavaScript, etc.). Usually, if we have RESTful API `$resource` will help us communicate with the server in Active Record like fashion. Although, in some applications the data entities returned by the server are not in the most appropriate format, which we want to use in the front-end. @@ -1157,8 +1157,8 @@ function ObserverExample(ObserverService, $timeout, $scope) { ## References 1. [Wikipedia](https://en.wikipedia.org/wiki). The source of all brief descriptions of the design patterns is Wikipedia. -2. [AngularJS' documentation](https://docs.angularjs.org) -3. [AngularJS' git repository](https://github.com/angular/angular.js) +2. [Angular 1' documentation](https://docs.angularjs.org) +3. [Angular 1' git repository](https://github.com/angular/angular.js) 4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) 5. [Patterns of Enterprise Application Architecture (P of EAA)](http://martinfowler.com/books/eaa.html) 6. [Using Dependency Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) From e68ac33a35aa04c13f2fbf71c247c9a053e6234d Mon Sep 17 00:00:00 2001 From: mgechev Date: Wed, 1 Feb 2017 15:37:36 -0800 Subject: [PATCH 28/29] docs: angular 1 to angularjs --- README.md | 132 +++++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index ddf20cc..655e40b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Angular 1 in Patterns +# AngularJS in Patterns @@ -7,14 +7,14 @@ * [Translations](#translations) * [Abstract](#abstract) * [Introduction](#introduction) -* [Angular 1 overview](#angular-1-overview) +* [AngularJS overview](#angular-1-overview) * [Partials](#partials) * [Controllers](#controllers) * [Scope](#scope) * [Directives](#directives) * [Filters](#filters) * [Services](#services) -* [Angular 1 Patterns](#angular-1-patterns) +* [AngularJS Patterns](#angular-1-patterns) * [Services](#services-1) * [Singleton](#singleton) * [Factory Method](#factory-method) @@ -53,19 +53,19 @@ One of the best ways to learn something new is to see how the things you already know are used in it. This document does not intend to make its readers familiar with the design or architectural patterns; it suggests basic understanding of the concepts of the OOP, design patterns and architectural patterns. -The goal of this paper is to describe how different software design and architectural patterns are applied in Angular 1 or any Angular 1 single-page application. +The goal of this paper is to describe how different software design and architectural patterns are applied in AngularJS or any AngularJS single-page application. ## Introduction -The document begins with brief overview of the Angular 1 framework. The overview explains the main Angular 1 components - directives, filters, controllers, services, scope. The second section lists and describes different design and architectural patterns, which are implemented inside the framework. The patterns are grouped by the Angular 1 component they are used in. If some patterns are used inside multiple components it will be explicitly mentioned. +The document begins with brief overview of the AngularJS framework. The overview explains the main AngularJS components - directives, filters, controllers, services, scope. The second section lists and describes different design and architectural patterns, which are implemented inside the framework. The patterns are grouped by the AngularJS component they are used in. If some patterns are used inside multiple components it will be explicitly mentioned. -The last section contains a few architectural patterns, which are commonly used inside most of the single-page applications built with Angular 1. +The last section contains a few architectural patterns, which are commonly used inside most of the single-page applications built with AngularJS. -## Angular 1 overview +## AngularJS overview -Angular 1 is a JavaScript framework developed by Google. It intends to provide a solid base for the development of CRUD Single-Page Applications (SPA). +AngularJS is a JavaScript framework developed by Google. It intends to provide a solid base for the development of CRUD Single-Page Applications (SPA). SPA is a web application, which once loaded, does not require full page reload when the user performs any actions with it. This means that all application resources (data, templates, scripts, styles) should be loaded with the initial request or better - the information and resources should be loaded on demand. -Since most of the CRUD applications has common characteristics and requirements, Angular 1 intends to provide the optimal set of them out-of-the-box. A few important features of Angular 1 are: +Since most of the CRUD applications has common characteristics and requirements, AngularJS intends to provide the optimal set of them out-of-the-box. A few important features of AngularJS are: - two-way data binding - dependency injection @@ -73,7 +73,7 @@ Since most of the CRUD applications has common characteristics and requirements, - testability - abstraction -The separation of concerns is achieved by dividing each Angular 1 application into separate components, such as: +The separation of concerns is achieved by dividing each AngularJS application into separate components, such as: - partials - controllers @@ -85,9 +85,9 @@ These components can be grouped inside different modules, which helps to achieve ### Partials -The partials are HTML strings. They may contain Angular 1 expressions inside the elements or their attributes. One of the distinctions between Angular 1 and the others frameworks is the fact that Angular 1' 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). +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 Angular 1 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. +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. **Sample partial** @@ -105,11 +105,11 @@ Initially each SPA loads `index.html` file. In the case of Angular 1 this file c ``` -With Angular 1 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. +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. ### Controllers -The Angular 1 controllers are JavaScript functions, which help handling the user interactions with the web application (for example mouse events, keyboard events, etc.), by attaching methods to the *scope*. All required external, for the controllers, components are provided through the Dependency Injection mechanism of Angular 1. The controllers are also responsible for providing the *model* to the partials by attaching data to the *scope*. We can think of this data as *view model*. +The AngularJS controllers are JavaScript functions, which help handling the user interactions with the web application (for example mouse events, keyboard events, etc.), by attaching methods to the *scope*. All required external, for the controllers, components are provided through the Dependency Injection mechanism of AngularJS. The controllers are also responsible for providing the *model* to the partials by attaching data to the *scope*. We can think of this data as *view model*. ```JavaScript function MyController($scope) { @@ -128,13 +128,13 @@ For example, if we wire the sample controller above with the partial provided in 1. 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. 2. 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 Angular 1 *directives* if they are previously defined as ones. +All the custom elements, attributes, comments or classes could be recognized as AngularJS *directives* if they are previously defined as ones. ### Scope -In Angular 1 scope is a JavaScript object, which is exposed to the partials. The scope could contain different properties - primitives, objects or methods. All methods attached to the scope could be invoked by evaluation of Angular 1 expression inside the partials associated with the given scope or direct call of the method by any component, which keeps reference to the scope. By using appropriate *directives*, the data attached to the scope could be binded to the view in such a way that each change in the partial will reflect a scope property and each change of a scope property will reflect the partial. +In AngularJS scope is a JavaScript object, which is exposed to the partials. The scope could contain different properties - primitives, objects or methods. All methods attached to the scope could be invoked by evaluation of AngularJS expression inside the partials associated with the given scope or direct call of the method by any component, which keeps reference to the scope. By using appropriate *directives*, the data attached to the scope could be binded to the view in such a way that each change in the partial will reflect a scope property and each change of a scope property will reflect the partial. -Another important characteristics of the scopes of any Angular 1 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. +Another important characteristics 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: @@ -165,7 +165,7 @@ With `div#child` is associated `ChildCtrl` but since the scope injected inside ` ### Directives -In Angular 1 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 directive or consider refactoring of already existing one, which could handle the required DOM manipulations. +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 directive 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: - template @@ -202,11 +202,11 @@ myModule.directive('alertButton', function () { In the example above the tag `` will be replaced button element. When the user clicks on the button the string `42` will be alerted. -Since the intent of this paper is not to explain the complete API of Angular 1, we will stop with the directives here. +Since the intent of this paper is not to explain the complete API of AngularJS, we will stop with the directives here. ### Filters -The filters in Angular 1 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. +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 a definition of a sample filter, which changes the given string to uppercase: @@ -257,11 +257,11 @@ function MyCtrl(Developer) { } ``` -## Angular 1 Patterns +## AngularJS Patterns -In the next a couple of sections, we are going to take a look how the traditional design and architectural patterns are composed in the Angular 1 components. +In the next a couple of sections, we are going to take a look how the traditional design and architectural patterns are composed in the AngularJS components. -In the last chapter we are going to take a look at some architectural patterns, which are frequently used in the development of Single-Page Applications with (but not limited to) Angular 1. +In the last chapter we are going to take a look at some architectural patterns, which are frequently used in the development of Single-Page Applications with (but not limited to) AngularJS. ### Services @@ -273,16 +273,16 @@ In the UML diagram bellow is illustrated the singleton design pattern. ![Singleton](https://rawgit.com/mgechev/angular-in-patterns/master/images/singleton.svg "Fig. 1") -When given dependency is required by any component, Angular 1 resolves it using the following algorithm: +When given dependency is required by any component, AngularJS resolves it using the following algorithm: - Takes its name and makes a lookup at a hash map, which is defined into a lexical closure (so it has a private visibility). -- If the dependency exists Angular 1 pass it as parameter to the component, which requires it. +- If the dependency exists AngularJS pass it as parameter to the component, which requires it. - If the dependency does not exists: - - Angular 1 instantiate it by calling the factory method of its provider (i.e. `$get`). Note that instantiating the dependency may require recursive call to the same algorithm, for resolving all the dependencies required by the given dependency. This process may lead to circular dependency. - - Angular 1 caches it inside the hash map mentioned above. - - Angular 1 passes it as parameter to the component, which requires it. + - AngularJS instantiate it by calling the factory method of its provider (i.e. `$get`). Note that instantiating the dependency may require recursive call to the same algorithm, for resolving all the dependencies required by the given dependency. This process may lead to circular dependency. + - AngularJS caches it inside the hash map mentioned above. + - AngularJS passes it as parameter to the component, which requires it. -We can take better look at the Angular 1' source code, which implements the method `getService`: +We can take better look at the AngularJS' source code, which implements the method `getService`: ```JavaScript function getService(serviceName) { @@ -347,7 +347,7 @@ In the code above we use the `config` callback in order to define new "provider" Each service, filter, directive and controller has a provider (i.e. object which factory method, called `$get`), which is responsible for creating the component's instance. -We can dig a little bit deeper in Angular 1' implementation: +We can dig a little bit deeper in AngularJS' implementation: ```JavaScript //... @@ -397,7 +397,7 @@ From the example above we can notice how the `$get` method is actually used: instanceInjector.invoke(provider.$get, provider, undefined, servicename) ``` -The snippet above calls the `invoke` method of `instanceInjector` with the factory method (i.e. `$get`) of given service, as first argument. Inside `invoke`'s body `annotate` is called with first argument the factory method. Annotate resolves all dependencies through the dependency injection mechanism of Angular 1, which was considered above. When all dependencies are resolved the factory method is being called: `fn.apply(self, args)`. +The snippet above calls the `invoke` method of `instanceInjector` with the factory method (i.e. `$get`) of given service, as first argument. Inside `invoke`'s body `annotate` is called with first argument the factory method. Annotate resolves all dependencies through the dependency injection mechanism of AngularJS, which was considered above. When all dependencies are resolved the factory method is being called: `fn.apply(self, args)`. If we think in terms of the UML diagram above we can call the provider a "ConcreteCreator" and the actual component, which is being created a "Product". @@ -413,7 +413,7 @@ There are a few benefits of using the factory method pattern in this case, becau ![Decorator](https://rawgit.com/mgechev/angular-in-patterns/master/images/decorator.svg "Fig. 4") -Angular 1 provides out-of-the-box way for extending and/or enhancing the functionality of already existing services. Using the method `decorator` of `$provide` you can create "wrapper" of any service you have previously defined or used by a third-party: +AngularJS provides out-of-the-box way for extending and/or enhancing the functionality of already existing services. Using the method `decorator` of `$provide` you can create "wrapper" of any service you have previously defined or used by a third-party: ```JavaScript myModule.controller('MainCtrl', function (foo) { @@ -442,10 +442,10 @@ myModule.config(function ($provide) { }); }); ``` -The example above defines new service called `foo`. In the `config` callback is called the method `$provide.decorator` with first argument `"foo"`, which is the name of the service, we want to decorate and second argument factory function, which implements the actual decoration. `$delegate` keeps reference to the original service `foo`. Using the dependency injection mechanism of Angular 1, reference to this local dependency is passed as first argument of the constructor function. +The example above defines new service called `foo`. In the `config` callback is called the method `$provide.decorator` with first argument `"foo"`, which is the name of the service, we want to decorate and second argument factory function, which implements the actual decoration. `$delegate` keeps reference to the original service `foo`. Using the dependency injection mechanism of AngularJS, reference to this local dependency is passed as first argument of the constructor function. We decorate the service by overriding its method `bar`. The actual decoration is simply extending `bar` by invoking one more `console.log statement` - `console.log('Decorated');` and after that call the original `bar` method with the appropriate context. -Using this pattern is especially useful when we need to modify the functionality of third party services. In cases when multiple similar decorations are required (like performance measurement of multiple methods, authorization, logging, etc.), we may have a lot of duplications and violate the DRY principle. In such cases it is useful to use [aspect-oriented programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming). The only AOP framework for Angular 1 I'm aware of could be found at [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop). +Using this pattern is especially useful when we need to modify the functionality of third party services. In cases when multiple similar decorations are required (like performance measurement of multiple methods, authorization, logging, etc.), we may have a lot of duplications and violate the DRY principle. In such cases it is useful to use [aspect-oriented programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming). The only AOP framework for AngularJS I'm aware of could be found at [github.com/mgechev/angular-aop](https://github.com/mgechev/angular-aop). #### Facade @@ -461,7 +461,7 @@ Using this pattern is especially useful when we need to modify the functionality ![Facade](https://rawgit.com/mgechev/angular-in-patterns/master/images/facade.svg "Fig. 11") -There are a few facades in Angular 1. Each time you want to provide higher level API to given functionality you practically create a facade. +There are a few facades in AngularJS. Each time you want to provide higher level API to given functionality you practically create a facade. For example, lets take a look how we can create an `XMLHttpRequest` POST request: @@ -482,7 +482,7 @@ http.onreadystatechange = function () { } http.send(params); ``` -But if we want to post this data using the Angular 1' `$http` service we can: +But if we want to post this data using the AngularJS' `$http` service we can: ```JavaScript $http({ @@ -518,7 +518,7 @@ We can distinguish three different types of proxy: - Remote Proxy - Protection Proxy -In this sub-chapter we are going to take a look at Angular 1' implementation of Virtual Proxy. +In this sub-chapter we are going to take a look at AngularJS' implementation of Virtual Proxy. In the snippet bellow, there is a call to the `get` method of `$resource` instance, called `User`: @@ -530,7 +530,7 @@ console.log(user); //{} `console.log` would outputs an empty object. Since the AJAX request, which happens behind the scene, when `User.get` is invoked, is asynchronous, we don't have the actual user when `console.log` is called. Just after `User.get` makes the GET request it returns an empty object and keeps reference to it. We can think of this object as virtual proxy (a simple placeholder), which would be populated with the actual data once the client receives response by the server. -How does this works with Angular 1? Well, lets consider the following snippet: +How does this works with AngularJS? Well, lets consider the following snippet: ```JavaScript function MainCtrl($scope, $resource) { @@ -542,7 +542,7 @@ function MainCtrl($scope, $resource) { ```html ``` -Initially when the snippet above executes, the property `user` of the `$scope` object will be with value an empty object (`{}`), which means that `user.name` will be undefined and nothing will be rendered. Internally Angular 1 will keep reference to this empty object. Once the server returns response for the get request, Angular 1 will populate the object with the data, received from the server. During the next `$digest` loop Angular 1 will detect change in `$scope.user`, which will lead to update of the view. +Initially when the snippet above executes, the property `user` of the `$scope` object will be with value an empty object (`{}`), which means that `user.name` will be undefined and nothing will be rendered. Internally AngularJS will keep reference to this empty object. Once the server returns response for the get request, AngularJS will populate the object with the data, received from the server. During the next `$digest` loop AngularJS will detect change in `$scope.user`, which will lead to update of the view. #### Active Record @@ -550,9 +550,9 @@ Initially when the snippet above executes, the property `user` of the `$scope` o ![Active Record](https://rawgit.com/mgechev/angular-in-patterns/master/images/active-record.svg "Fig. 7") -Angular 1 defines a service called `$resource`. In the current version of Angular 1 (1.2+) it is being distributed in module outside of the Angular 1' core. +AngularJS defines a service called `$resource`. In the current version of AngularJS (1.2+) it is being distributed in module outside of the AngularJS' core. -According to the Angular 1' documentation `$resource` is: +According to the AngularJS' documentation `$resource` is: >A factory which creates a resource object that lets you interact with RESTful server-side data sources. >The returned resource object has action methods which provide high-level behaviors without the need to interact with the low level $http service. @@ -577,9 +577,9 @@ This way we can use the constructor function and its static methods by: User.get({ userid: userid }); ``` -The code above will immediately return an empty object and keep reference to it. Once the response have been successfully returned and parsed, Angular 1 will populate this object with the received data (see [proxy](#proxy)). +The code above will immediately return an empty object and keep reference to it. Once the response have been successfully returned and parsed, AngularJS will populate this object with the received data (see [proxy](#proxy)). -You can find more details for `$resource` [The magic of $resource](http://blog.mgechev.com/2014/02/05/angularjs-resource-active-record-http/) and [Angular 1' documentation](https://docs.angularjs.org/api/ngResource/service/$resource). +You can find more details for `$resource` [The magic of $resource](http://blog.mgechev.com/2014/02/05/angularjs-resource-active-record-http/) and [AngularJS' documentation](https://docs.angularjs.org/api/ngResource/service/$resource). Since Martin Fowler states that @@ -595,7 +595,7 @@ Since Martin Fowler states that In some cases you need to do some kind of pre and/or post processing of HTTP requests. In the case of the Intercepting Filters you pre/post process given HTTP request/response in order to include logging, security or any other concern, which is influenced by the request body or headers. Basically the Intercepting Filters pattern include a chain of filters, each of which process data in given order. The output of each filter is input of the next one. -In Angular 1 we have the idea of the Intercepting Filters in `$httpProvider`. `$httpProvider` has an array property called `interceptors`, which contains a list of objects. Each object may have properties called: `request`, `response`, `requestError`, `responseError`. +In AngularJS we have the idea of the Intercepting Filters in `$httpProvider`. `$httpProvider` has an array property called `interceptors`, which contains a list of objects. Each object may have properties called: `request`, `response`, `requestError`, `responseError`. `requestError` is an interceptor, which gets called when a previous interceptor threw an error or resolved with a rejection, respectively `responseError` is being called when the previous `response` interceptor has thrown an error. @@ -628,7 +628,7 @@ According to the Gang of Four, MVC is nothing more than combination of: - Composite - Observer -They state that the view is composition of components. In Angular 1 the situation is similar. Our views are formed by a composition of directives and DOM elements, on which these directives could be applied. +They state that the view is composition of components. In AngularJS the situation is similar. Our views are formed by a composition of directives and DOM elements, on which these directives could be applied. Lets look at the following example: @@ -671,8 +671,8 @@ In the second, JavaScript, example we see that the `template` property of the di ![Interpreter](https://rawgit.com/mgechev/angular-in-patterns/master/images/interpreter.svg "Fig. 6") -Behind its `$parse` service, Angular 1 provides its own implementation of interpreter of a DSL (Domain Specific Language). The used DSL is simplified and modified version of JavaScript. -The main differences between the JavaScript expressions and Angular 1 expressions that Angular 1 expressions: +Behind its `$parse` service, AngularJS provides its own implementation of interpreter of a DSL (Domain Specific Language). The used DSL is simplified and modified version of JavaScript. +The main differences between the JavaScript expressions and AngularJS expressions that AngularJS expressions: - may contain filters with UNIX like pipe syntax - don't throw any errors @@ -690,7 +690,7 @@ var Parser; Once given expression have been tokenized it is cached internally, because of performance concerns. -The terminal expressions in the Angular 1 DSL are defined as follows: +The terminal expressions in the AngularJS DSL are defined as follows: ```JavaScript var OPERATORS = { @@ -725,9 +725,9 @@ var OPERATORS = { We can think of the function associated with each terminal as implementation of the `AbstractExpression`'s interface. -Each `Client` interprets given Angular 1 expression in a specific context - specific scope. +Each `Client` interprets given AngularJS expression in a specific context - specific scope. -Few sample Angular 1 expressions are: +Few sample AngularJS expressions are: ```JavaScript // toUpperCase filter is applied to the result of the expression @@ -770,8 +770,8 @@ For example if we evaluate the template above in the context of the following ob baz ``` -Angular 1 templates are actually HTML, they are not in an intermediate format like the traditional templates are. -What Angular 1 compiler does is to traverse the DOM tree and look for already known directives (elements, attributes, classes or even comments). When Angular 1 finds any of these directives it invokes the logic associated with them, which may involve evaluation of different expressions in the context of the current scope. +AngularJS templates are actually HTML, they are not in an intermediate format like the traditional templates are. +What AngularJS compiler does is to traverse the DOM tree and look for already known directives (elements, attributes, classes or even comments). When AngularJS finds any of these directives it invokes the logic associated with them, which may involve evaluation of different expressions in the context of the current scope. For example: @@ -798,9 +798,9 @@ will produce the same result as the one above. The main difference here is that ![Observer](https://rawgit.com/mgechev/angular-in-patterns/master/images/observer.svg "Fig. 7") -There are two basic ways of communication between the scopes in an Angular 1 application. The first one is calling methods of parent scope by a child scope. This is possible since the child scope inherits prototypically by its parent, as mentioned above (see [Scope](#scope)). This allows communication in a single direction - child to parent. Some times it is necessary to call method of given child scope or notify it about a triggered event in the context of the parent scope. Angular 1 provides built-in observer pattern, which allows this. Another possible use case, of the observer pattern, is when multiple scopes are interested in given event but the scope, in which context the event is triggered, is not aware of them. This allows decoupling between the different scopes, non of the scopes should be aware of the rest of the scopes. +There are two basic ways of communication between the scopes in an AngularJS application. The first one is calling methods of parent scope by a child scope. This is possible since the child scope inherits prototypically by its parent, as mentioned above (see [Scope](#scope)). This allows communication in a single direction - child to parent. Some times it is necessary to call method of given child scope or notify it about a triggered event in the context of the parent scope. AngularJS provides built-in observer pattern, which allows this. Another possible use case, of the observer pattern, is when multiple scopes are interested in given event but the scope, in which context the event is triggered, is not aware of them. This allows decoupling between the different scopes, non of the scopes should be aware of the rest of the scopes. -Each Angular 1 scope has public methods called `$on`, `$emit` and `$broadcast`. The method `$on` accepts topic as first argument and callback as second. We can think of the callback as an observer - an object, which implements the `Observer` interface (in JavaScript the functions are first-class, so we can provide only implementation of the `notify` method): +Each AngularJS scope has public methods called `$on`, `$emit` and `$broadcast`. The method `$on` accepts topic as first argument and callback as second. We can think of the callback as an observer - an object, which implements the `Observer` interface (in JavaScript the functions are first-class, so we can provide only implementation of the `notify` method): ```JavaScript function ExampleCtrl($scope) { @@ -836,7 +836,7 @@ For a best practice example see [Observer Pattern as an External Service](#obser ![Chain of Responsibilities](https://rawgit.com/mgechev/angular-in-patterns/master/images/chain-of-responsibilities.svg "Fig. 5") -As stated above the scopes in an Angular 1 application form a hierarchy known as the scope chain. Some of the scopes are "isolated", which means that they don't inherit prototypically by their parent scope, but are connected to it via their `$parent` property. +As stated above the scopes in an AngularJS application form a hierarchy known as the scope chain. Some of the scopes are "isolated", which means that they don't inherit prototypically by their parent scope, but are connected to it via their `$parent` property. When `$emit` or `$broadcast` are called we can think of the scope chain as event bus, or even more accurately chain of responsibilities. Once the event is triggered it is emitted downwards or upwards (depending on the method, which was called). Each subsequent scope may: @@ -873,7 +873,7 @@ The different handlers from the UML diagram above are the different scopes, inje ![Command](https://rawgit.com/mgechev/angular-in-patterns/master/images/command.svg "Fig. 11") -Before continuing with the application of the command pattern lets describe how Angular 1 implements data binding. +Before continuing with the application of the command pattern lets describe how AngularJS implements data binding. When we want to bind our model to the view we use the directives `ng-bind` (for single-way data binding) and `ng-model` (for two-way data binding). For example, if we want each change in the model `foo` to reflect the view we can: @@ -881,7 +881,7 @@ When we want to bind our model to the view we use the directives `ng-bind` (for ``` -Now each time we change the value of `foo` the inner text of the span will be changed. We can achieve the same effect with more complex Angular 1 expressions, like: +Now each time we change the value of `foo` the inner text of the span will be changed. We can achieve the same effect with more complex AngularJS expressions, like: ```html @@ -889,7 +889,7 @@ Now each time we change the value of `foo` the inner text of the span will be ch In the example above the value of the span will be the concatenated uppercased value of `foo` and `bar`. What happens behind the scene? -Each `$scope` has method called `$watch`. When the Angular 1 compiler find the directive `ng-bind` it creates a new watcher of the expression `foo + ' ' + bar | uppercase`, i.e. `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`. The callback will be triggered each time the value of the expression change. In the current case the callback will update the value of the span. +Each `$scope` has method called `$watch`. When the AngularJS compiler find the directive `ng-bind` it creates a new watcher of the expression `foo + ' ' + bar | uppercase`, i.e. `$scope.$watch("foo + ' ' + bar | uppercase", function () { /* body */ });`. The callback will be triggered each time the value of the expression change. In the current case the callback will update the value of the span. Here are the first a couple of lines of the implementation of `$watch`: @@ -908,7 +908,7 @@ $watch: function(watchExp, listener, objectEquality) { //... ``` -We can think of the `watcher` object as a command. The expression of the command is being evaluated on each [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) loop. Once Angular 1 detects change in the expression, it invokes the `listener` function. The `watcher` command encapsulates the whole information required for watching given expression and delegates the execution of the command to the `listener` (the actual receiver). We can think of the `$scope` as the command's `Client` and the `$digest` loop as the command's `Invoker`. +We can think of the `watcher` object as a command. The expression of the command is being evaluated on each [`"$digest"`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) loop. Once AngularJS detects change in the expression, it invokes the `listener` function. The `watcher` command encapsulates the whole information required for watching given expression and delegates the execution of the command to the `listener` (the actual receiver). We can think of the `$scope` as the command's `Client` and the `$digest` loop as the command's `Invoker`. ### Controllers @@ -922,11 +922,11 @@ According to [4](#references) the page controller: >Page Controller pattern accept input from the page request, invoke the requested actions on the model, and determine the correct view to use for the resulting page. Separate the dispatching logic from any view-related code -Since there is a lot of duplicate behavior between the different pages (like rendering footers, headers, taking care of the user's session, etc.) page controllers can form a hierarchy. In Angular 1 we have controllers, which are with more limited scope of responsibilities. They don't accept user requests, since this is responsibility of the `$route` or `$state` services and the page rendering is responsibility of the directives `ng-view`/`ui-view`. +Since there is a lot of duplicate behavior between the different pages (like rendering footers, headers, taking care of the user's session, etc.) page controllers can form a hierarchy. In AngularJS we have controllers, which are with more limited scope of responsibilities. They don't accept user requests, since this is responsibility of the `$route` or `$state` services and the page rendering is responsibility of the directives `ng-view`/`ui-view`. -Similarly to the page controllers, Angular 1 controllers handle user interactions, provide and update the models. The model is exposed to the view when it is being attached to the scope, all methods invoked by the view, in result of user actions, are ones, which are already attached to the scope. Another similarity between the page controllers and the Angular 1 controllers is the hierarchy, which they form. It corresponds to the scope hierarchy. That way common actions can be isolated to the base controllers. +Similarly to the page controllers, AngularJS controllers handle user interactions, provide and update the models. The model is exposed to the view when it is being attached to the scope, all methods invoked by the view, in result of user actions, are ones, which are already attached to the scope. Another similarity between the page controllers and the AngularJS controllers is the hierarchy, which they form. It corresponds to the scope hierarchy. That way common actions can be isolated to the base controllers. -The controllers in Angular 1 are quite similar to the code-behind in ASP.NET WebForms, since their responsibilities almost overlap. +The controllers in AngularJS are quite similar to the code-behind in ASP.NET WebForms, since their responsibilities almost overlap. Here is an example hierarchy between few controllers: ```HTML @@ -995,7 +995,7 @@ In the example above we have IIFE (Immediately-Invoked Function Expression), whi In this case the user of the `Page` object doesn't has direct access to the `title` variable, which is defined inside the local scope of the IIFE. -The module pattern is very useful when defining services in Angular 1. Using this pattern we can simulate (and actually achieve) privacy: +The module pattern is very useful when defining services in AngularJS. Using this pattern we can simulate (and actually achieve) privacy: ```javascript app.factory('foo', function () { @@ -1024,7 +1024,7 @@ Once we want to inject `foo` inside any other component we won't be able to use ![Data Mapper](https://rawgit.com/mgechev/angular-in-patterns/master/images/data-mapper.svg "Fig. 10") -As the description above states, the data mapper is used for bidirectional transfer of data between a persistent data store and an in memory data representation. Usually our Angular 1 application communicates with API server, which is written in any server-side language (Ruby, PHP, Java, JavaScript, etc.). +As the description above states, the data mapper is used for bidirectional transfer of data between a persistent data store and an in memory data representation. Usually our AngularJS application communicates with API server, which is written in any server-side language (Ruby, PHP, Java, JavaScript, etc.). Usually, if we have RESTful API `$resource` will help us communicate with the server in Active Record like fashion. Although, in some applications the data entities returned by the server are not in the most appropriate format, which we want to use in the front-end. @@ -1157,8 +1157,8 @@ function ObserverExample(ObserverService, $timeout, $scope) { ## References 1. [Wikipedia](https://en.wikipedia.org/wiki). The source of all brief descriptions of the design patterns is Wikipedia. -2. [Angular 1' documentation](https://docs.angularjs.org) -3. [Angular 1' git repository](https://github.com/angular/angular.js) +2. [AngularJS' documentation](https://docs.angularjs.org) +3. [AngularJS' git repository](https://github.com/angular/angular.js) 4. [Page Controller](http://msdn.microsoft.com/en-us/library/ff649595.aspx) 5. [Patterns of Enterprise Application Architecture (P of EAA)](http://martinfowler.com/books/eaa.html) 6. [Using Dependency Injection to Avoid Singletons](http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html) From 73896f5aacaa3dbe882105336768cd88cedb18e2 Mon Sep 17 00:00:00 2001 From: Octavio Carpes Date: Sat, 19 May 2018 14:12:46 -0300 Subject: [PATCH 29/29] Uptade README.md Added a coma to this line: "Every piece of logic, which doesn't belong to the components described above, should be placed inside a service." --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 655e40b..70b8952 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ function MyCtrl(uppercaseFilter) { ### Services -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. +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. ```JavaScript myModule.service('Developer', function () {