Note: for support questions, please use one of these channels: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports.
BUG
When using bindToController properties are being set before the constructor is invoked.
bindToController: {
min: '@',
}
class MyController {
private _min : number;
constructor() {
this._min = 0;
}
get min() {
return this._min;
}
set min(value : number) {
this._min = value;
}
}
Constructor should be called before properties are set.
Behaviour in properties setters that rely on values initialized in the constructor fail.
Windows 7, TypeScript 1.8.2, chrome 49.0.2623.87, Angular 1.5.0
The constructor is called in the following lines after the properties have been set in nodeLinkFn.
var controllerResult = controller();
if (controllerResult !== controller.instance) {
The properties setters created using Object.defineProperty are invoked even though the constructor has not yet been invoked. This is problematic if the property setters rely on values initialized in the constructor (in my case throwing exceptions).
This actually a feature not a bug :)
We are binding the properties, so you can work right away from the constructor with them.
(Note: This only works with regular functions not classes (but your TypeScript Class obviously gets transpiled to a plain old ES5 constructor function).)
Maybe you could check if _min
is undefined before assigning a default value in the constructor.
With respect, I would suggest breaking the semantics of classes is a BUG and not a feature. As ECMA 6 becomes standard with property getters and setters this type of issue will become more common. It is called a "constructor" after all and controllers in all other areas of angular would have their constructors called before any other interaction (AFAIK).
I understand that it is difficult to understand how many people may be relying on this "feature" and that changing the behavior would probably have to be supported through an option for backward compatibility.
My issue was not with the constructor throwing an error, it was with the property setter assuming the state of the object was initialized and valid (the specific code is not shown in the example).
Maybe you could check if _min is undefined before assigning a default value in the constructor.
I think that would fail if you had something like this:
class MyController {
private _min : number = 0;
constructor() { }
}
The resulting JS automatically sets _min in the constructor function:
var MyController = (function () {
function MyController() {
this._min = 0;
}
return MyController;
}());
I appreciate people are taking time out to provide workarounds; however, I'm not really looking for a work around; to be frank, its a trivial issue to resolve and I probably shouldn't be writing Javascript if I can't workaround it:).
I'm simply here to report the bug, which I still class as a bug. An APIs success is measured on its consistency. Every special case goes against this; an effects productivity, controllers in angular everywhere else are dealt with as types (with constructors), they are not functions.
As docs says in: https://docs.angularjs.org/api/ng/service/$compile :
Deprecation warning: although bindings for non-ES6 class controllers are currently bound to this before the controller constructor is called, this use is now deprecated. Please place initialization code that relies upon bindings inside a $onInit method on the controller, instead
May be it should be changed ASAP. 驴Is there any scheduled change yet?
@drpicox Thanks for finding that; it sounds like the API is going to change to be consistent, as you say; the only question is when?
While I agree with this idea in principle, you do also have to keep in mind that while the change is trivial, it's also potentially massively breaking for any historical code that uses assignments within the constructor; $onInit
didn't exist until extremely recently.
That deprecation was introduced in 1.5, so I would expect that a breaking change of this magnitude would not be made until 1.6 or even 1.7 (Should really be in a major version, but Angular2 kinda killed that possibility it seems like without causing massive confusion).
I haven't though about this for more than 15 seconds (quite literally), but another interesting idea would be that if a controller lacks on $onInit
method, then the constructor is invoked when $onInit
would have been. Both occur at roughly the same time prior to prelinking, and I'm not sure whether or not there would even be any observable side effects from that. That could possibly allow this to be introduced in a non-breaking way.
if a controller lacks on $onInit method, then the constructor is invoked when $onInit would have been
I haven't thought about for more than 15 seconds either (quite literally), but I think this would break the expectation that all controllers are initialized when $onInit
is called.
I get a thought to this "issue", and I think that may be there is a mid-way between break compatibility or do interfere in the use some ES5 features (like getters and setters).
There are some examples in angular, that because some reason a breaking change is introduced, but there is a possible configuration that enables old/new behaviour. For example:
$compileProvider.debugInfoEnabled(false);
and {strictDi: true}
So, it might be something like this would be an option:
$compileProvider.bindToControllerBeforeConstructor(false);
and change the default in later versions.
As @dpogue points out, this behavior breaks a common pattern (default true and binding to false).
This "feature" irrevocably breaks the understanding of how constructors work.
I continue to fail to see how binding properties to an instance is a sensible operation when, by definition, the instance is in an undefined state before a constructor finishes executing. A reasonable flow would be [constructor(), bindings, $onInit].
Thanks for your feedback everyone. Here is a little more context (which will hopefully give better perspective of the situation):
A reasonable flow would be [constructor(), bindings, $onInit].
$onInit
has only recently been introduced.
I continue to fail to see how binding properties to an instance is a sensible operation
I will make more sense if you stop thinking were we are and focus on how we got there and how to get out of there :smiley:
_How did we get here?_
controller as
/ditch-the-scope paradigm and people started switching to it._How do we get out of here?_
There was no doubt that this "feature" came at a cost (which is highlighted more by the rise of classes, since it is not compatible with them). Nobody wanted to be there, but we didn't have anywhere to go.
(Note that the feature has been marked as deprecated for quite some time.)
Things have changed and we _do_ now have the means to get out (thanks to controller lifecycle hooks). Still we should get out of there without breaking all these apps that are currently (reluctantly or not) relying on this "feature".
So, here is the general plan (there are still some details to figure out):
this behavior breaks a common pattern
tl;dr
One supported pattern is no compensation for a ton of unhappy users. But we have a plan... :wink:
In defense of the "feature", I believe it has served as very well and we would have been in a worse situation without it - but it's time to go now :smiley:
@gkalpak Thank you _so much_ for that write up! Having been with Angular since 0.9, I kind of decided to forget about a lot of that pain; and point #4 especially was a trap I personally never fell in to. Being reminded of it is humbling :)
This is not strictly related to ES6, but I also use ES5 Object property getter/setters that expose "private" values and have a similar issue.
For this use case, it would be good if the $onInit function was called with the bindings as a parameter, rather than having them set directly on the controller after they are parsed.
Here's an excerpt:
function ThingController() {
var _name;
function $onInit(bindings) {
_name = bindings.name;
}
controllerProperties.name = {
get: function get() {
return _name;
}
};
controllerProperties.otherThing = {
get: function get() {
return "Example of something else " + _name;
}
};
}
With #15095 merged, our plan has been materialized. It will be possible to opt-out of bindings pre-assignment from v1.5.9v1.5.10 on (not yet released) and "no pre-assignment" will be the default behavior in v1.6.0 (with the ability to opt-in).
Was the option to opt-out included? There is no preAssignBindingsEnabled-function in $compileProvider in v.1.5.9.
@emillunde, we had to cut an interim 1.5.9 release that contains mostly security fixes. This feature will be included in 1.5.10 (which should be released pretty soon).
@gkalpak - Just to confirm, does this change introduced in 1.6 just delay the assignment of bindings
to the controller instance, until just before $onInit
is called?
Please let me know if you need me to clarify the question.
@ashclarke, exactly.
In 1.5.10+, the default behavior will be to assign before calling the constructor, but you can change it to delay until before $onInit
.
In 1.6.0+ the default behavior will be to delay until before $onInit
, but you can change it to assign before calling the constructor.
In 1.7.0+ it will delay until before $onInit
(and you won't be able to change that).
Most helpful comment
With respect, I would suggest breaking the semantics of classes is a BUG and not a feature. As ECMA 6 becomes standard with property getters and setters this type of issue will become more common. It is called a "constructor" after all and controllers in all other areas of angular would have their constructors called before any other interaction (AFAIK).
I understand that it is difficult to understand how many people may be relying on this "feature" and that changing the behavior would probably have to be supported through an option for backward compatibility.
My issue was not with the constructor throwing an error, it was with the property setter assuming the state of the object was initialized and valid (the specific code is not shown in the example).