Created
January 20, 2012 21:51
-
-
Save vojtajina/1649788 to your computer and use it in GitHub Desktop.
Angular: BC module for scope/controller separation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @license AngularJS | |
* (c) 2010-2012 AngularJS http://angularjs.org | |
* License: MIT | |
*/ | |
/** | |
* Backward compatibility module for AngularJS | |
* @author Vojta Jina <[email protected]> | |
* | |
* Load this module to enable old-style controllers, where controller and scope are mixed together. | |
* | |
* This module decorates Angular's $controller service: | |
* - if given controller does not ask for $scope, it instantiates it in old-way | |
* - if given controller does ask for $scope, instantiation is delegated to default $controller | |
* service. | |
* | |
* This also allows migrating apps step by step. | |
*/ | |
angular.module('ngScopeController', ['ng'], ['$provide', function($provide) { | |
$provide.decorator('$controller', ['$injector', '$delegate', '$parse', '$window', | |
function($injector, $delegate, $parse, $window) { | |
var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/m; | |
var FN_ARG_SPLIT = /,/; | |
var FN_ARG = /^\s*(.+?)\s*$/; | |
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; | |
/** | |
* Return a list of arguments to inject | |
* If no $inject property specified it parse the argument names. | |
* @param fn | |
*/ | |
function inferInjectionArgs(fn) { | |
// assert fn is a function | |
if (!angular.isFunction(fn)) { | |
var error = new Error("Controller must be a function, got " + | |
(typeof fn === 'object' ? fn.constructor.name || 'Object' : typeof fn)); | |
throw error; | |
} | |
if (fn.$inject) return fn.$inject; | |
// guess from argument names | |
var args = []; | |
var fnText = fn.toString().replace(STRIP_COMMENTS, ''); | |
var argDecl = fnText.match(FN_ARGS); | |
angular.forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { | |
arg.replace(FN_ARG, function(all, name) { | |
args.push(name); | |
}); | |
}); | |
return args; | |
} | |
/** | |
* $controller service | |
* | |
* @param {Function|string} Class Constructor function of the controller or string id. | |
* @param {Object} locals Locals for injecting, must contain a $scope. | |
* @return {Object} Instance of the controller. | |
*/ | |
return function(Class, locals) { | |
var scope = locals.$scope; | |
// given string id, find ctrl on window or current scope | |
if (angular.isString(Class)) { | |
var getter = $parse(Class); | |
Class = getter(scope) || getter($window); | |
} | |
var injectArgs = inferInjectionArgs(Class); | |
// asking for scope - delegate to original service | |
if (injectArgs.indexOf('$scope') !== -1) { | |
return $delegate(Class, locals); | |
} | |
// not asking for scope - BC hack | |
var classPrototype = Class.prototype; | |
for(var key in classPrototype) { | |
scope[key] = angular.bind(scope, classPrototype[key]); | |
} | |
$injector.invoke(Class, scope, locals); | |
return scope; | |
}; | |
}]); | |
}]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe('$controller', function() { | |
var $controller; | |
beforeEach(module('ngScopeController')); | |
beforeEach(inject(function($injector) { | |
$controller = $injector.get('$controller'); | |
})); | |
it('should return instance of given controller class', function() { | |
var MyClass = function($scope) {}, | |
ctrl = $controller(MyClass, {$scope: {}}); | |
expect(ctrl).toBeDefined(); | |
expect(ctrl instanceof MyClass).toBe(true); | |
}); | |
it('should inject arguments allowing locals', inject(function($http) { | |
var MyClass = function($scope, $http, $location) { | |
this.$scope = $scope; | |
this.$http = $http; | |
this.$location = $location; | |
}; | |
var scope = {}, | |
localLocation = {}, | |
ctrl = $controller(MyClass, {$scope: scope, $location: localLocation}); | |
expect(ctrl.$http).toBe($http); | |
expect(ctrl.$scope).toBe(scope); | |
expect(ctrl.$location).toBe(localLocation); | |
})); | |
it('should get controller from $window', inject(function($window) { | |
var ctrl; | |
$window.a = { | |
Some: function() {this.id = 'Some';}, | |
Other: function($scope) {this.id = 'Other';} | |
}; | |
ctrl = $controller('a.Some', {$scope: {}}); | |
expect(ctrl).toBeDefined(); | |
expect(ctrl.id).toBe('Some'); | |
ctrl = $controller('a.Other', {$scope: {}}); | |
expect(ctrl instanceof $window.a.Other).toBe(true); | |
expect(ctrl.id).toBe('Other'); | |
})); | |
it('should get controller from current scope', function() { | |
var ctrl, scope = {}; | |
scope.a = { | |
Some: function() {this.id = 'Some';}, | |
Other: function($scope) {this.id = 'Other';} | |
}; | |
ctrl = $controller('a.Some', {$scope: scope}); | |
expect(ctrl).toBeDefined(); | |
expect(ctrl.id).toBe('Some'); | |
ctrl = $controller('a.Other', {$scope: scope}); | |
expect(ctrl instanceof scope.a.Other).toBe(true); | |
expect(ctrl.id).toBe('Other'); | |
}); | |
it('should export all methods to scope if scope not injected', function() { | |
var MyClass = function() { | |
this.prop1 = 1; | |
this.method1 = function(value) { | |
this.prop1 = 2; | |
return value; | |
}; | |
}; | |
MyClass.prototype = { | |
prop2: 1, | |
method2: function(value) { | |
this.prop2 = 2; | |
return value; | |
} | |
}; | |
var scope = {}, | |
ctrl = $controller(MyClass, {$scope: scope}); | |
expect(ctrl).toBe(scope); | |
expect(scope.prop1).toBe(1); | |
expect(scope.prop2).toBe(1); | |
expect(scope.method1(true)).toBe(true); | |
expect(scope.method2(true)).toBe(true); | |
expect(scope.prop1).toBe(2); | |
expect(scope.prop2).toBe(2); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you support IE8 and below you need the following fix for the Array.indexOf method:
https://gist.github.com/2379920