Skip to content

Instantly share code, notes, and snippets.

@ThomasBurleson
Forked from vojtajina/angular-bc.js
Created February 7, 2012 22:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ThomasBurleson/1762635 to your computer and use it in GitHub Desktop.
Save ThomasBurleson/1762635 to your computer and use it in GitHub Desktop.
Angular: decorates/intercepts $controller service
/**
* @license AngularJS
* (c) 2010-2012 AngularJS http://angularjs.org
* License: MIT
*/
/**
* Backward compatibility module for AngularJS
* @author Vojta Jina <vojta.jina@gmail.com>
*
* 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', function($injector, $delegate) {
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} Class Constructor function of the controller.
* @param {Object} scope Related scope.
* @return {Object} Instance of the controller.
*/
return function(Class, scope) {
var injectArgs = inferInjectionArgs(Class);
// asking for scope - delegate to original service
if (injectArgs.indexOf('$scope') !== -1) {
return $delegate(Class, scope);
}
// 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);
return scope;
};
}]);
}]);
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);
expect(ctrl).toBeDefined();
expect(ctrl instanceof MyClass).toBe(true);
});
it('should inject arguments and given scope', inject(function($http) {
var MyClass = function($scope, $http) {
this.$scope = $scope;
this.$http = $http;
};
var scope = {},
ctrl = $controller(MyClass, scope);
expect(ctrl.$http).toBe($http);
expect(ctrl.$scope).toBe(scope);
}));
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);
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