You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It's often the case when using dependency injection in JavaScript, to want to close over class constructor arguments in instance methods, rather than assign them to private fields and access them via 'this'. Here is an example of an AngularJS controller that does this:
functionMyController($scope/* plus 10 or so others, sometimes 15, in a real application */){this.canSave=function(){return!$scope.myForm.$dirty&&!$scope.myForm.$error;};}MyController.$inject=['$scope'];
Here is this controller expressed as a TypeScript class:
As you can see, it's a lot more ceremony than is necessary. You can clean it up only by making canSave a prototype method, and assigning $scope and all your other dependencies as private fields:
But this is not equivalent to the first example in javascript. You're forced to create private variables for your injected constructor arguments. What I'd like to do is use the same light-weight constructor argument syntax as other languages like Scala, F#, and C# 6, where the arguments are next to the class name:
Proposed new syntax
// NEW syntax:classMyController($scope: ng.IScope){canSave=()=>!$scope.myForm.$dirty&&!$scope.myForm.$error;}
Of course, if you wanted to still use prototype functions instead of instance functions, you still could:
This syntax makes it really nice to define small DTO-like object classes with an absolute minimum of ceremony:
classNode{}classBinaryOperator(publicname: string)extendsNode{}classAdditionOperatorextendsBinaryOperatorconstructor{super('+');}}// even less ceremony, potentially:classSubtractionOperatorextendsBinaryOperator('-'){}
Prior Art
As mentioned above, this syntax is available in a number of other excellent languages. Here are three:
Scala
Scala eliminates the constructor function entirely. The class definition itself is the constructor:
classGreeter(message: String) {
println("This code is in the constructor.")
defSayHi() = println(message)
}
Scala can also define members inline in the constructor argument list, just like TypeScript:
classGreeter(valmessage:String) {
defsayHi() = println(this.message) // println(message) also works
}
F#
F# is similar to Scala. do bindings must occur before member definitions but can appear before or after let bindings. I'm not sure if it allows inline definition of members in the argument list. Apologies if the syntax is a little off, my F# isn't that great:
typeGreeter(message: string)=do printfn "This code is in the constructor."letfield="foo"memberthis.SayHi= printfn message
C# 6
C# 6 has a primary constructor syntax. An empty block defines the constructor body. However you can't define members directly from the argument list:
class Greeter(stringmessage){{
Console.WriteLine("This code is in the constructor. message was: "+message);}publicstringMessage{ get;}= message;// because magic? hidden instance field?publicvoidSayHi(){// I'm not exactly sure how member functions gain access to constructor arguments
Console.WriteLine("message: "+message);}}
My proposal is most heavily influenced by Scala and C# 6. Like Scala, you can define members directly from the argument list (TypeScript could already do this), but like C# 6 and unlike Scala, the primary constructor body code must be contained within a single code block instead of just any code in the class definition block. In C# this is just an empty block, but in my proposed syntax, we retain the 'constructor' keyword preceeding the constructor body, but without arguments.
You could potentially get closer to Scala's constructor body syntax (which eliminates the extra syntax ceremony of a constructor block). I'm not sure if you'd want to (or if you could). But, if we did, I'd take inspiration from F#'s ordering requirements and ensure all constructor body code to appear before the first prototype member definition:
// a probably controversial alternative, similar to F# do bindings.classMyController($scope: ng.IScope,$log: ng.ILogService){$log.info('MyController was instantiated!');field=$scope.myForm;// yeah... this is probably ambiguous syntax. assigning a global or definining an instance field? That's why I went with the constructor block syntax instead.$log.info('initial value of myForm.$dirty: '+$scope.myForm.$dirty.toString());sayHi(){alert('hello!');}// ERROR: primary constructor statements must appear before the first prototype member definition// $log.info('can't do this');}
The text was updated successfully, but these errors were encountered:
It's a fair point, but I think the risk is relatively low. It's difficult to imagine this syntax being adopted as an ES standard. First, there is already a constructor syntax, so some would view it as redundant. Secondly, unlike typescript, regular JS doesn't need to declare fields first before assigning them in the constructor.
In ES, I would just write this, which isn't too bad:
Also, I don't think adding default parameters to the constructor argument list is a sufficient or desirable workaround. It changes the signature of the constructor which may have architectural side effects.
It may be that the risk is relatively low, but then the upside is relatively low (slightly more convenient syntax, no change in expressivity) while the downside is potentially high (breaking old TS code to maintain compatibility with some future ES version). I definitely get the desire for features like this and am personally accustomed to this one in particular from other languages I use but we do have to be judicious with expression level syntax additions.
Motivation
It's often the case when using dependency injection in JavaScript, to want to close over class constructor arguments in instance methods, rather than assign them to private fields and access them via 'this'. Here is an example of an AngularJS controller that does this:
Here is this controller expressed as a TypeScript class:
As you can see, it's a lot more ceremony than is necessary. You can clean it up only by making canSave a prototype method, and assigning $scope and all your other dependencies as private fields:
But this is not equivalent to the first example in javascript. You're forced to create private variables for your injected constructor arguments. What I'd like to do is use the same light-weight constructor argument syntax as other languages like Scala, F#, and C# 6, where the arguments are next to the class name:
Proposed new syntax
Of course, if you wanted to still use prototype functions instead of instance functions, you still could:
And, if you still need to run code in the constructor, re-use the constructor syntax but omit the argument list:
This syntax makes it really nice to define small DTO-like object classes with an absolute minimum of ceremony:
Prior Art
As mentioned above, this syntax is available in a number of other excellent languages. Here are three:
Scala
Scala eliminates the constructor function entirely. The class definition itself is the constructor:
Scala can also define members inline in the constructor argument list, just like TypeScript:
F#
F# is similar to Scala. do bindings must occur before member definitions but can appear before or after let bindings. I'm not sure if it allows inline definition of members in the argument list. Apologies if the syntax is a little off, my F# isn't that great:
C# 6
C# 6 has a primary constructor syntax. An empty block defines the constructor body. However you can't define members directly from the argument list:
My proposal is most heavily influenced by Scala and C# 6. Like Scala, you can define members directly from the argument list (TypeScript could already do this), but like C# 6 and unlike Scala, the primary constructor body code must be contained within a single code block instead of just any code in the class definition block. In C# this is just an empty block, but in my proposed syntax, we retain the 'constructor' keyword preceeding the constructor body, but without arguments.
You could potentially get closer to Scala's constructor body syntax (which eliminates the extra syntax ceremony of a constructor block). I'm not sure if you'd want to (or if you could). But, if we did, I'd take inspiration from F#'s ordering requirements and ensure all constructor body code to appear before the first prototype member definition:
The text was updated successfully, but these errors were encountered: