Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 42e622b

Browse files
committed
revert: refactor($compile): remove preAssignBindingsEnabled leftovers
This reverts commit 8e104ee. This internal clean-up turned out to break popular UI libraries (e.g. `ngMaterial`, `ui-bootstrap`) and cause pain to developers. Fixes #16594 Closes #16595
1 parent ad0ba99 commit 42e622b

File tree

5 files changed

+167
-67
lines changed

5 files changed

+167
-67
lines changed

src/auto/injector.js

+26-4
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,12 @@ var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
7070
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
7171
var $injectorMinErr = minErr('$injector');
7272

73+
function stringifyFn(fn) {
74+
return Function.prototype.toString.call(fn);
75+
}
76+
7377
function extractArgs(fn) {
74-
var fnText = Function.prototype.toString.call(fn).replace(STRIP_COMMENTS, ''),
78+
var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
7579
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
7680
return args;
7781
}
@@ -910,6 +914,19 @@ function createInjector(modulesToLoad, strictDi) {
910914
return args;
911915
}
912916

917+
function isClass(func) {
918+
// Support: IE 9-11 only
919+
// IE 9-11 do not support classes and IE9 leaks with the code below.
920+
if (msie || typeof func !== 'function') {
921+
return false;
922+
}
923+
var result = func.$$ngIsClass;
924+
if (!isBoolean(result)) {
925+
result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func));
926+
}
927+
return result;
928+
}
929+
913930
function invoke(fn, self, locals, serviceName) {
914931
if (typeof locals === 'string') {
915932
serviceName = locals;
@@ -921,9 +938,14 @@ function createInjector(modulesToLoad, strictDi) {
921938
fn = fn[fn.length - 1];
922939
}
923940

924-
// http://jsperf.com/angularjs-invoke-apply-vs-switch
925-
// #5388
926-
return fn.apply(self, args);
941+
if (!isClass(fn)) {
942+
// http://jsperf.com/angularjs-invoke-apply-vs-switch
943+
// #5388
944+
return fn.apply(self, args);
945+
} else {
946+
args.unshift(null);
947+
return new (Function.prototype.bind.apply(fn, args))();
948+
}
927949
}
928950

929951

src/ng/compile.js

+73-57
Original file line numberDiff line numberDiff line change
@@ -2790,6 +2790,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
27902790
};
27912791
}
27922792

2793+
if (controllerDirectives) {
2794+
elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
2795+
}
2796+
27932797
if (newIsolateScopeDirective) {
27942798
// Initialize isolate scope bindings for new isolate scope directive.
27952799
compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
@@ -2805,69 +2809,53 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
28052809
}
28062810
}
28072811

2808-
if (controllerDirectives) {
2809-
elementControllers = createMap();
2810-
for (var name in controllerDirectives) {
2811-
var directive = controllerDirectives[name];
2812-
var locals = {
2813-
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
2814-
$element: $element,
2815-
$attrs: attrs,
2816-
$transclude: transcludeFn
2817-
};
2818-
2819-
var controllerConstructor = directive.controller;
2820-
if (controllerConstructor === '@') {
2821-
controllerConstructor = attrs[name];
2822-
}
2812+
// Initialize bindToController bindings
2813+
for (var name in elementControllers) {
2814+
var controllerDirective = controllerDirectives[name];
2815+
var controller = elementControllers[name];
2816+
var bindings = controllerDirective.$$bindings.bindToController;
28232817

2824-
var instance = $controller(controllerConstructor, locals, directive.controllerAs);
2825-
2826-
$element.data('$' + name + 'Controller', instance);
2827-
2828-
// Initialize bindToController bindings
2829-
var bindings = directive.$$bindings.bindToController;
2830-
var bindingInfo = initializeDirectiveBindings(controllerScope, attrs, instance, bindings, directive);
2831-
2832-
elementControllers[name] = { instance: instance, bindingInfo: bindingInfo };
2818+
controller.instance = controller();
2819+
$element.data('$' + controllerDirective.name + 'Controller', controller.instance);
2820+
controller.bindingInfo =
2821+
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
28332822
}
28342823

2835-
// Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
2836-
forEach(controllerDirectives, function(controllerDirective, name) {
2837-
var require = controllerDirective.require;
2838-
if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
2839-
extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
2840-
}
2841-
});
2824+
// Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
2825+
forEach(controllerDirectives, function(controllerDirective, name) {
2826+
var require = controllerDirective.require;
2827+
if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
2828+
extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
2829+
}
2830+
});
28422831

2843-
// Handle the init and destroy lifecycle hooks on all controllers that have them
2844-
forEach(elementControllers, function(controller) {
2845-
var controllerInstance = controller.instance;
2846-
if (isFunction(controllerInstance.$onChanges)) {
2847-
try {
2848-
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
2849-
} catch (e) {
2850-
$exceptionHandler(e);
2851-
}
2852-
}
2853-
if (isFunction(controllerInstance.$onInit)) {
2854-
try {
2855-
controllerInstance.$onInit();
2856-
} catch (e) {
2857-
$exceptionHandler(e);
2858-
}
2859-
}
2860-
if (isFunction(controllerInstance.$doCheck)) {
2861-
controllerScope.$watch(function() { controllerInstance.$doCheck(); });
2862-
controllerInstance.$doCheck();
2832+
// Handle the init and destroy lifecycle hooks on all controllers that have them
2833+
forEach(elementControllers, function(controller) {
2834+
var controllerInstance = controller.instance;
2835+
if (isFunction(controllerInstance.$onChanges)) {
2836+
try {
2837+
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
2838+
} catch (e) {
2839+
$exceptionHandler(e);
28632840
}
2864-
if (isFunction(controllerInstance.$onDestroy)) {
2865-
controllerScope.$on('$destroy', function callOnDestroyHook() {
2866-
controllerInstance.$onDestroy();
2867-
});
2841+
}
2842+
if (isFunction(controllerInstance.$onInit)) {
2843+
try {
2844+
controllerInstance.$onInit();
2845+
} catch (e) {
2846+
$exceptionHandler(e);
28682847
}
2869-
});
2870-
}
2848+
}
2849+
if (isFunction(controllerInstance.$doCheck)) {
2850+
controllerScope.$watch(function() { controllerInstance.$doCheck(); });
2851+
controllerInstance.$doCheck();
2852+
}
2853+
if (isFunction(controllerInstance.$onDestroy)) {
2854+
controllerScope.$on('$destroy', function callOnDestroyHook() {
2855+
controllerInstance.$onDestroy();
2856+
});
2857+
}
2858+
});
28712859

28722860
// PRELINKING
28732861
for (i = 0, ii = preLinkFns.length; i < ii; i++) {
@@ -2995,6 +2983,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
29952983
return value || null;
29962984
}
29972985

2986+
function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
2987+
var elementControllers = createMap();
2988+
for (var controllerKey in controllerDirectives) {
2989+
var directive = controllerDirectives[controllerKey];
2990+
var locals = {
2991+
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
2992+
$element: $element,
2993+
$attrs: attrs,
2994+
$transclude: transcludeFn
2995+
};
2996+
2997+
var controller = directive.controller;
2998+
if (controller === '@') {
2999+
controller = attrs[directive.name];
3000+
}
3001+
3002+
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
3003+
3004+
// For directives with element transclusion the element is a comment.
3005+
// In this case .data will not attach any data.
3006+
// Instead, we save the controllers for the element in a local hash and attach to .data
3007+
// later, once we have the actual element.
3008+
elementControllers[directive.name] = controllerInstance;
3009+
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
3010+
}
3011+
return elementControllers;
3012+
}
3013+
29983014
// Depending upon the context in which a directive finds itself it might need to have a new isolated
29993015
// or child scope created. For instance:
30003016
// * if the directive has been pulled into a template because another directive with a higher priority

src/ng/controller.js

+41-1
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,16 @@ function $ControllerProvider() {
8181
* It's just a simple call to {@link auto.$injector $injector}, but extracted into
8282
* a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
8383
*/
84-
return function $controller(expression, locals, ident) {
84+
return function $controller(expression, locals, later, ident) {
8585
// PRIVATE API:
86+
// param `later` --- indicates that the controller's constructor is invoked at a later time.
87+
// If true, $controller will allocate the object with the correct
88+
// prototype chain, but will not invoke the controller until a returned
89+
// callback is invoked.
8690
// param `ident` --- An optional label which overrides the label parsed from the controller
8791
// expression, if any.
8892
var instance, match, constructor, identifier;
93+
later = later === true;
8994
if (ident && isString(ident)) {
9095
identifier = ident;
9196
}
@@ -111,6 +116,41 @@ function $ControllerProvider() {
111116
assertArgFn(expression, constructor, true);
112117
}
113118

119+
if (later) {
120+
// Instantiate controller later:
121+
// This machinery is used to create an instance of the object before calling the
122+
// controller's constructor itself.
123+
//
124+
// This allows properties to be added to the controller before the constructor is
125+
// invoked. Primarily, this is used for isolate scope bindings in $compile.
126+
//
127+
// This feature is not intended for use by applications, and is thus not documented
128+
// publicly.
129+
// Object creation: http://jsperf.com/create-constructor/2
130+
var controllerPrototype = (isArray(expression) ?
131+
expression[expression.length - 1] : expression).prototype;
132+
instance = Object.create(controllerPrototype || null);
133+
134+
if (identifier) {
135+
addIdentifier(locals, identifier, instance, constructor || expression.name);
136+
}
137+
138+
return extend(function $controllerInit() {
139+
var result = $injector.invoke(expression, instance, locals, constructor);
140+
if (result !== instance && (isObject(result) || isFunction(result))) {
141+
instance = result;
142+
if (identifier) {
143+
// If result changed, re-assign controllerAs value to scope.
144+
addIdentifier(locals, identifier, instance, constructor || expression.name);
145+
}
146+
}
147+
return instance;
148+
}, {
149+
instance: instance,
150+
identifier: identifier
151+
});
152+
}
153+
114154
instance = $injector.instantiate(expression, locals, constructor);
115155

116156
if (identifier) {

src/ngMock/angular-mocks.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -2345,11 +2345,14 @@ angular.mock.$RootElementProvider = function() {
23452345
*/
23462346
function createControllerDecorator() {
23472347
angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
2348-
return function(expression, locals, bindings, ident) {
2349-
if (angular.isString(bindings)) ident = bindings;
2350-
var instance = $delegate(expression, locals, ident);
2351-
angular.extend(instance, bindings);
2352-
return instance;
2348+
return function(expression, locals, later, ident) {
2349+
if (later && typeof later === 'object') {
2350+
var instantiate = $delegate(expression, locals, true, ident);
2351+
var instance = instantiate();
2352+
angular.extend(instance, later);
2353+
return instance;
2354+
}
2355+
return $delegate(expression, locals, later, ident);
23532356
};
23542357
}];
23552358

test/auto/injectorSpec.js

+19
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,25 @@ describe('injector', function() {
482482
expect(instance).toEqual(new Clazz('a-value'));
483483
expect(instance.aVal()).toEqual('a-value');
484484
});
485+
486+
they('should detect ES6 classes regardless of whitespace/comments ($prop)', [
487+
'class Test {}',
488+
'class Test{}',
489+
'class //<--ES6 stuff\nTest {}',
490+
'class//<--ES6 stuff\nTest {}',
491+
'class {}',
492+
'class{}',
493+
'class //<--ES6 stuff\n {}',
494+
'class//<--ES6 stuff\n {}',
495+
'class/* Test */{}',
496+
'class /* Test */ {}'
497+
], function(classDefinition) {
498+
// eslint-disable-next-line no-eval
499+
var Clazz = eval('(' + classDefinition + ')');
500+
var instance = injector.invoke(Clazz);
501+
502+
expect(instance).toEqual(jasmine.any(Clazz));
503+
});
485504
}
486505
});
487506

0 commit comments

Comments
 (0)