├── .gitignore ├── LICENSE ├── README.md ├── client ├── index.html ├── js │ ├── app.js │ ├── lib │ │ ├── angular.1.0.7.js │ │ ├── es5-sham.2.1.0.js │ │ └── es5-shim.2.1.0.js │ └── pageController │ │ ├── baseMixin.js │ │ ├── baseNoPrototypeFunctionality.js │ │ ├── basePrototypeFunctionality.js │ │ ├── inheritByInjector.js │ │ ├── inheritPrototypically.js │ │ ├── mixinByInjector.js │ │ └── mixinWithPrototypicalInheritance.js └── partial │ ├── generic.html │ └── home.html ├── index.js ├── package.json └── server └── express.js /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .project 3 | *sublime-* 4 | deploy 5 | node_modules 6 | npm-* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Reason [reason -A- exratione.com] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the 'Software'), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJS Controller Inheritance 2 | ================================ 3 | 4 | This package contains some experiments with controller inheritance in AngularJS, 5 | and a trivial Node.js Express server to view the experiments as a web site. 6 | 7 | How to Manage Similar Pages in AngularJS 8 | ---------------------------------------- 9 | 10 | So your application has a set of pages that are quite similar. They share 11 | functionality of one form or another, which might differ a little between the 12 | pages but is fairly similar in overall form. 13 | 14 | There are a couple of approaches you might take in this situation to maximize 15 | code reuse and clarity: 16 | 17 | * Move functionality into services, where it can be appropriately factored. 18 | * Aggressively carve up the pages into directives and aim for directive reuse. 19 | * Create an inheritance hierarchy of page controllers and place common functionality in ancestor controllers. 20 | 21 | All of these are valid approaches, each more or less useful than the others in 22 | some circumstances, and can be mixed and matched to suit. 23 | 24 | Here we're going to look at methods of creating controller hierarchies. 25 | 26 | Controller Inheritance via $injector 27 | ------------------------------------ 28 | 29 | An example: 30 | 31 | ``` 32 | /** 33 | * Define a parent controller. 34 | * 35 | * @param {Object} $scope 36 | */ 37 | function ParentController($scope) { 38 | // Only create functions if they haven't already been set by the child. 39 | this.decorateScope = this.decorateScope || function () { 40 | $scope.decorator = 23; 41 | } 42 | 43 | this.initialize = this.initialize || function () { 44 | this.decorateScope(); 45 | } 46 | } 47 | ``` 48 | 49 | ``` 50 | /** 51 | * Create a child controller, using the $injector to pull in parent definitions. 52 | * 53 | * @param {Object} $injector 54 | * @param {Object} $scope 55 | */ 56 | function ChildController($injector, $scope) { 57 | // Override the parent function while allowing children to further override 58 | // it. 59 | this.decorateScope = this.decorateScope || function () { 60 | $scope.decorator = 24; 61 | } 62 | 63 | $injector.invoke(ParentController, this, { 64 | $scope: $scope 65 | }); 66 | } 67 | ``` 68 | 69 | Mixins work in the same way as inheritance: 70 | 71 | ``` 72 | /** 73 | * Create a child controller inheriting from several parents. 74 | * 75 | * @param {Object} $injector 76 | * @param {Object} $scope 77 | */ 78 | function ChildController($injector, $scope) { 79 | // Any number of other controllers can be invoked in this way. 80 | $injector.invoke(ParentController, this, { 81 | $scope: $scope 82 | }); 83 | $injector.invoke(MixinController, this, { 84 | $scope: $scope 85 | }); 86 | } 87 | ``` 88 | 89 | In summary: 90 | 91 | * All dependencies must be passed explicitly to $injector.invoke(), or else test code will break because injected mocks will be ignored by the parent class. The parent will load the non-mock instances for anything not explicitly set in $injector.invoke(). 92 | * Unlike prototypical inheritance, parents must explicitly permit overriding. 93 | * A mixin works in exactly the same way as inheritance. 94 | * You can't define functionality in the controller constructor prototype. That is ignored by $injector.invoke(). You must define functions inside the constructor body, as shown above. 95 | 96 | Controller Prototype Inheritance 97 | -------------------------------- 98 | 99 | To enable standard-issue Javascript prototypical inheritance we should define an 100 | inherits() function somewhere easily accessible: 101 | 102 | ``` 103 | /** 104 | * A clone of the Node.js util.inherits() function. This will require 105 | * browser support for the ES5 Object.create() method. 106 | * 107 | * @param {Function} ctor 108 | * The child constructor. 109 | * @param {Function} superCtor 110 | * The parent constructor. 111 | */ 112 | angular.inherits = function (ctor, superCtor) { 113 | ctor.super_ = superCtor; 114 | ctor.prototype = Object.create(superCtor.prototype, { 115 | constructor: { 116 | value: ctor, 117 | enumerable: false 118 | } 119 | }); 120 | }; 121 | ``` 122 | 123 | An example: 124 | 125 | ``` 126 | /** 127 | * Define a parent controller. 128 | * 129 | * @param {Object} $scope 130 | */ 131 | function ParentController($scope) { 132 | this.$scope = $scope; 133 | this.initialize(); 134 | } 135 | 136 | /** 137 | * Decorate the scope. 138 | */ 139 | ParentController.prototype.decorateScope = function () { 140 | this.$scope.decorator = 23; 141 | } 142 | 143 | /** 144 | * Initialize the controller for use. 145 | */ 146 | ParentController.prototype.initialize = function () { 147 | this.decorateScope(); 148 | } 149 | ``` 150 | 151 | ``` 152 | /** 153 | * Use standard prototypical inheritance for the child. 154 | * 155 | * @param {Object} $injector 156 | * @param {Object} $scope 157 | */ 158 | function ChildController($scope) { 159 | // No need to explicitly pass the injected dependencies, provided they 160 | // are ordered consistently. 161 | ChildController.super_.apply(this, arguments); 162 | } 163 | angular.inherits(ChildController, ParentController); 164 | 165 | /** 166 | * Override the parent function. 167 | * @see ParentController#decorateScope 168 | */ 169 | ChildController.prototype.decorateScope = function () { 170 | this.$scope.decorator = 24; 171 | } 172 | ``` 173 | 174 | Mixins can be created in a number of ways. For example, by calling the mixin 175 | constructor and coping over its prototype functions. 176 | 177 | ``` 178 | function ChildController($scope) { 179 | // Invoke the parent constructor. 180 | ChildController.super_.apply(this, arguments); 181 | 182 | // Invoke the mixin constructor. You don't get any of the mixin's 183 | // prototype methods by doing this, and are also less likely to be able to 184 | // structure arguments to as to be able to use apply(), so back to passing 185 | // dependencies explicitly. 186 | MixinController.call(this, $scope); 187 | } 188 | angular.inherits(ChildController, ParentController); 189 | 190 | // Add the mixin prototype functionality to the ChildController prototype, 191 | // provided it doesn't already exist - i.e. is overridden. 192 | angular.forEach(MixinController.prototype, function (value, name) { 193 | if (ChildController.prototype[name] === undefined) { 194 | ChildController.prototype[name] = value; 195 | } 196 | }); 197 | ``` 198 | 199 | * This method cannot be mixed with the $injector method, as $injector.invoke() does not copy over prototype functionality from parent to child. 200 | * With suitable structuring of dependency parameter order there is no need to explicitly pass injected dependencies. 201 | * Mixins are somewhat graceless: call() the constructor function and clone prototype properties. 202 | * This method requires browser support for Object.create(), such as via es5-sham.js in older browsers. 203 | 204 | Directive Controller Inheritance 205 | -------------------------------- 206 | 207 | All of the notes above apply equally to inheritance of controllers associated 208 | with directives rather than pages. 209 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularJS: Examples of Controller Inheritance 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /client/js/app.js: -------------------------------------------------------------------------------- 1 | /*global 2 | angular: false 3 | */ 4 | 5 | (function () { 6 | 'use strict'; 7 | 8 | // Create the example application AngularJS module. 9 | var inheritance = angular.module('inheritance', []); 10 | 11 | // Use the config function to set up routes. 12 | inheritance.config(function ($routeProvider) { 13 | $routeProvider 14 | .when('/home', { 15 | templateUrl: '/partial/home.html' 16 | }) 17 | .when('/inheritByInjector', { 18 | controller: 'inheritByInjectorController', 19 | templateUrl: '/partial/generic.html' 20 | }) 21 | .when('/inheritPrototypically', { 22 | controller: 'inheritPrototypicallyController', 23 | templateUrl: '/partial/generic.html' 24 | }) 25 | .when('/mixinByInjector', { 26 | controller: 'mixinByInjectorController', 27 | templateUrl: '/partial/generic.html' 28 | }) 29 | .when('/mixinWithPrototypicalInheritance', { 30 | controller: 'mixinWithPrototypicalInheritanceController', 31 | templateUrl: '/partial/generic.html' 32 | }) 33 | .otherwise({ 34 | redirectTo: '/home' 35 | }); 36 | }); 37 | 38 | // Add a prototypical inheritance function. Note that this will need es5-sham 39 | // in older browsers to provide Object.create(). 40 | inheritance.inherits = function (ctor, superCtor) { 41 | ctor.super_ = superCtor; 42 | ctor.prototype = Object.create(superCtor.prototype, { 43 | constructor: { 44 | value: ctor, 45 | enumerable: false 46 | } 47 | }); 48 | }; 49 | 50 | })(); 51 | -------------------------------------------------------------------------------- /client/js/lib/es5-sham.2.1.0.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009-2012 by contributors, MIT License 2 | // vim: ts=4 sts=4 sw=4 expandtab 3 | 4 | // Module systems magic dance 5 | (function (definition) { 6 | // RequireJS 7 | if (typeof define == "function") { 8 | define(definition); 9 | // YUI3 10 | } else if (typeof YUI == "function") { 11 | YUI.add("es5-sham", definition); 12 | // CommonJS and