When I create instance of a class via Object.create(Example.prototype) to bypass its constructor, it is no longer possible to work with native private fields:
class Example {
#myPrivateProp;
setup() {
this.#myPrivateProp = 1;
}
}
const e1 = new Example();
const e2 = Object.create(Example.prototype);
console.log(e1.setup()); // works
console.log(e2.setup()); // fails with Uncaught TypeError: Cannot write private member #myPrivateProp to an object whose class did not declare it
Is there a way to make this work, while maintining the invariant of not calling the constructor?
For context you can see this issue: https://github.com/mikro-orm/mikro-orm/issues/1226
What you are defining here is not a private field but a private instance field. A private instance field is there not to be exposed and manipulated by others except that very instance. You shouldn't be able to clone that very instance along with it's state, manipulate it and replace the original one.
You have two options;
A static private field that belongs to the Example class which can be accessed and manipulated mutually by any instance of that class or even by objects created like Object.create(Example.prototype).
such as;
class Example {
static #SECRET = 0;
constructor(){
}
addOne() {
return ++Example.#SECRET;
}
}
i1 = new Example();
i2 = new Example();
console.log(i1.addOne()); // <- 1
console.log(i2.addOne()); // <- 2
i3 = Object.create(Example.prototype);
console.log(i3.addOne()); // <- 3
Example.#SECRET = 4; // <- Uncaught SyntaxError: Private field '#SECRET' must be
// declared in an enclosing class
The private instance field belongs to the instance and can only be accessed and manipulated by that very instance. How this is achieved is exactly seen in your example code. Every single instance will have it's own #myPrivateProp which, if you ask me, is a beautiful thing. For example one can instantiate many individual Queues and their operationaly critical properties wouldn't be exposed.
Related
I have a class I reuse, often, to the tune of possibly tens of thousands of instances in a given session. It occurred to me that creating all these properties within the constructor may be replication, that is each function is unique in memory and not a pointer, so I created a little test setup:
const testTree = function (){
console.log(this, this.konnichiwa);
}
const testFjord = function (aloha){
return function() {
console.log(this, aloha, this.konnichiwa);
}
}
class Clown extends Object{
constructor(props){
super(props);
const aloha = "Hello!"; //<- Private party
this.konnichiwa = "Also hello I think"; //<- Everyone's invited
this.testWan = () => {
console.log(this, aloha, this.konnichiwa);
}
this.testTree = testTree;
this.testFjord = testFjord(aloha);
}
testToo = () => {
console.log(this, this.konnichiwa);
}
}
//export default Clown; //this is an export in my application, used lots
const test = new Clown();
const otherTest = new Clown();
console.log(test.testWan === otherTest.testWan);
console.log(test.testToo === otherTest.testToo);
console.log(test.testTree === otherTest.testTree);
console.log(test.testFjord === otherTest.testFjord);
test.testWan();
test.testToo();
test.testTree();
test.testFjord();
Part 1
As you can test above, testWan, testToo, and testFjord are all unique per instance, but testTree is not. Is there any way to declare a "pointer"/"reusable function" but inside class constructor?
The issue here with testToo and testTree is that they can't access private vars within the constructor like testWan can. testFjord is a factory and can be passed these, but then the returned function is unique and won't be able to interact well with vars passed into it.
It's very likely not possible - I think it's a catch 22 scope thing - but you may know better. The only recourse I can think of is to add a property to this for each thing I need to use in testTree, but that exposes things I may not want exposed outside of the class.
Part 2
This part only applies if this is a generally consistent behavior, and not something completely unique per-browser. Does the engine hold onto references to things like conditionals (which I suspect are sorta anonymous-function-like behind the scenes) once the constructor has run?
I have a fairly knarly conditional setup I'm not going to shove in the code here. This is entirely within the constructor right now. I suspect that, although not a function declaration itself, it is also not a pointer, but an entirely fresh instance per the 'new' in new Clown. It needs to manipulate some private vars and so per Part 1 I haven't figured out a good way to extract this.
Example, there are references to private vars inside the constructor for exposed functions: aloha above is private but used by public testWan function, and so needs to be held after constructor has executed. Is the entire constructor held for the life of test & otherTest or is the constructor going to be dropped after use and just the reference to aloha held in memory?
When getting Data from a VueX Module via Getter the Object is wrapped with a Proxy that throws following error:
TypeError: attempted to get private field on non-instance
when trying to read a private property with a public getter.
class Example {
#privateField;
get privateField() {
return this.#privateField;
}
}
computed(){
getInfoForSpecificItem() {
const arrayOfInstantiatedExamples = this.$store.getters['getArrayOfInstantiatedExamples'];
for (let i = 0; i < arrayOfInstantiatedExamples.length; i += 1) {
// trying to access it in this condition throws the error since the Object
// has been wrapped with a Proxy coming from Vue (I guess)
if (arrayOfInstantiatedExamples[i].privateField === 'something') {
// does something
}
}
}
}
Why am I not able to read the property, and how can I create an instance from the proxy target.
logging the Proxy Object reveals, that the target has all information, yet trying to read it via getter doesn't work.
Are there any other options than making the fields public?
You are defining a class not a function. So remove the parentheses().
class Example {
You cannot define variable with #. Only $ and _ are allowed to use in a variable. So replace # with $ like this $privateField;
Classes are blueprints, not a valid object. First make an object from this class.
const proxyWrappedExample = new Example;
After you log this then you can avoid the error but it logs undefined.
Your code should look like
class Example {
$privateField = 'something';
get privateField() {
return this.$privateField;
}
}
const proxyWrappedExample = new Example;
// I receive the Object (which has been instantiated earlier) wrapped in a proxy Object.
// Trying to access the property throws the error.
// e.g.:
console.log(proxyWrappedExample.privateField); // 'something'
$privateField without any value will look like following.
class Example {
$privateField;
get privateField() {
return this.$privateField;
}
}
const proxyWrappedExample = new Example;
// I receive the Object (which has been instantiated earlier) wrapped in a proxy Object.
// Trying to access the property throws the error.
// e.g.:
console.log(proxyWrappedExample.privateField); // 'undefined'
I have a TypeScript model which contains properties and functions like:
class Person {
name: string
sayHello(){ console.log(name); }
}
When creating its instance I can make use of this sayHello() method as expected.
The problem comes when I store and get the object from the local storage or an external web service.
While the properties are still present the methods have disappeared. (obviously because it’s just text).
Is there any way to restore the completeness of the object to be able to use its functions?
Or otherwise, is there any other technique? For instance, helper class that can work with type.
When you save to a local storage, you will save a JSON representation of the class data; when you get the object back from the local storage, it will be just an object, not an instance of the original class.
You can look for an external library that will help you with this, but the simple solution is to create a new instance of the class and assign the field values to the class using Object.assign:
class Person {
public constructor(public name?: string){}
sayHello() { console.log(name); }
}
let p = new Person("Test");
let data = JSON.stringify(p);
let p2 = new Person()
Object.assign(p2, JSON.parse(data));
In Angular 2 testing utility I do something like this:
fixture = TestBed.createComponent(EditableValueComponent);
where EditableValueComponent is a normal component class.
I wonder how it works:
static createComponent<T>(component: Type<T>): ComponentFixture<T>;
Beceause I wanna do something similar (I want to simplify some testing stuff):
export class SuperFixture<T>
{
fixture: ComponentFixture<T>;
component: T;
constructor()
{
this.fixture = TestBed.createComponent(T); // <--- problem here!
this.component = this.fixture.componentInstance;
}
}
The problem is:
'T' only refers to a type, but is being used as a value here.'
EDIT #1
I solved the problem this way:
constructor(component)
{
this.fixture = TestBed.createComponent<T>(component);
But I still don't know how it works..
You still need to pass actual class (constructor function that creates instances of a class) into constructor function of SuperFixture. Under the hood TestBed.createComponent calls provided constructor function with new to create instance of provided class. So SuperClass signature might look like this:
class SuperFixture<T>
{
fixture: ComponentFixture<T>;
component: T;
// passing in the constructor for instances of T
constructor(componentConstructor: new () => T)
{
this.fixture = TestBed.createComponent<T>(componentConstructor);
this.component = this.fixture.componentInstance;
}
}
Was working on that answer, but had to step out for coffee. ¯_(ツ)_/¯
The language feature you are using is called a Generic in TypeScript. It allows defining types at runtime with "type variables" (like <T>), which are separate from function arguments.
Before, the type variable was being passed as a function argument, when the function expected an instance of type T. That's what the error means.
The change you made works because you are passing the type variable and the instance in their correct positions in the call.
The SuperFixture object gets the value of T when you create it, and then it will pass that type variable to createComponent in the constructor, along with the value of component.
I would like to implement prototypal inheritance in Angular where by base type is defined as an Angular value. The problem is setting my child type prototype. Suppose this simplified example:
File 1
angular.module("Test")
.value("BaseController", BaseController);
BaseController.$inject = [...];
function BaseController(...) {
...
}
BaseController.prototype.func = function () {
...
};
File 2
angular.module("Test")
.controller("ChildController", ChildController);
ChildController.$inject = ["BaseController", ...];
function ChildController(BaseController, ...) {
BaseController.call(this, ...);
// save reference for later
ChildController.base = BaseController;
...
}
// ERROR - Clearly why
ChildController.prototype = Object.create(BaseController.prototype);
// ERROR - instance properties aren't available on this level
ChildController.prototype = Object.create(ChildController.base.prototype);
Inheritance
The problem is that prototype is being generated before constructor is being instantiated. But until constructor is being instantiated I have no possible reference of the angular-injected BaseController.
The only way I can see to solve this is to have my BaseController publically defined so I can access it even before angular injects it into my constructor. I don't like this as I can't have my code private inside function closures and I would also like to use angular's features as much as possible without having a mixture of usual Javascript against Angular code.
Main question
Is there any way that I could make prototypal inheritance work by having base types defined as values (or other) in angular?
This solution is specifically for your approach. You can use the module's run block to assign the prototype. In File 2 add the following:
angular.module("Test").run(function(BaseController) {
ChildController.prototype = Object.create(BaseController.prototype);
});
BaseController gets injected and is available for creating the prototype. Since this code runs before any controller gets instantiated you get your prototypal inheritance.
Also keep in mind that ChildController.$inject has to contain all of BaseController.$inject.
An alternative would be to attach BaseController to the module itself:
angular.module("Test").BaseController = BaseController;
...
ChildController.prototype = Object.create(angular.module("Test").BaseController.prototype);
The code would still be private and the constructor function is still only accessible through the module.
You can also look for alternatives to inheritance. Depending on the situation hierarchical controllers might be a viable solution.
<div ng-controller="BaseController"><%-- Handle Generic stuff --%>
...
<div ng-controller="ChildController"><%-- Handle specific stuff --%>
If you are trying to extend controllers to create a mixin, then prototypal inheritance is not what you want to do.
For an example of extending controller behavior checkout this SO Question:
angular-extending-controller
Important info
This solution is implemented specifically for Angular-related code that's using actual Javascript prototypes to define controllers/services and not just anonymous functions as shown in everyday examples on the web - this means following John Papa's styleguide and extending it even further as he doesn't use real prototypes
All constructors must use explicit DI annotations using $inject static property. This limitation can be somewhat worked around by
using Angular injector's annotate (if annotations are provided inline - array) or
changing .inherits signature to include all base type's constructor parameters in correct order as its own parameters i.e.
Child.inherits(Base, baseInjection1, baseInjection2, ...)
Setting proper prototypal inheritance in Angular
I've come up with a rather simple and most of all generic way to set type inheritance of my Angular controllers. Well this inheritance goes even beyond that and could be used with any Angular asset that uses type constructors (controllers, services, etc.)
Resulting solution changes original files' contents to this superbly simplistic form:
File1
angular.module("Test")
.value("BaseController", BaseController);
BaseController.$inject = [...];
function BaseController(...) {
...
}
BaseController.prototype.func = function () {
...
};
File2
angular.module("Test")
.controller("ChildController", ChildController);
ChildController.$inject = ["BaseController", ...];
function ChildController(BaseController, ...) {
// ALL MAGIC IS HERE!
ChildController.inherits(BaseController, arguments);
...
}
So all we have to do is make one call in our child type's constructor and provide base type (that's injected for us by Angular DI into constructor) and child type's constructor parameters (so inheritance can use them when running base type's constructor).
Implementing .inherit functionality
To make things generic I've added this function to Function.prototype object so it becomes available to all functions (or better yet constructors). This is how it's implemented:
Function.prototype.inherits = function inherits(BaseType, constructorInjections) {
/// <summary>Sets type's inheritance to base type.</summary>
/// <param name="BaseType" type="Function" optional="false">Base type to set for this child type.</param>
/// <param name="constructorInjections" type="Array" optional="true">Child type constructor injection instances.</param>
// return if both angular types don't use explicit DI
if (!this.$inject || !BaseType.$inject) return;
// DRY
if (this.prototype.__proto__ === BaseType.prototype || Object.getPrototypeOf(this.prototype) === BaseType.prototype) return;
// #region construct base object instance
// make a quick-search dictionary of child constructor injections
for (var i = 0, l = this.$inject.length, dic = {}; i < l; i++)
{
dic[this.$inject[i]] = i;
}
// create base type's constructor injections array
for (var i = 0, l = BaseType.$inject.length, baseParams = []; i < l; i++)
{
baseParams.push(constructorInjections[dic[BaseType.$inject[i]]]);
}
// get base type's constructed instance
var baseInstance = BaseType.apply(baseInstance = {}, baseParams) || baseInstance;
// #endregion
// #region set type inheritance chain
if (Object.setPrototypeOf)
{
Object.setPrototypeOf(this.prototype, BaseType.prototype);
}
else
{
// doesn't do the same thing, but it should work just as well
angular.extend(this.prototype, BaseType.prototype, { __proto__: BaseType.prototype });
}
// #endregion
// #region add base class generated instance to prototype
for (var i = 0, keys = Object.keys(baseInstance), l = keys.length; i < l; i++)
{
this.prototype[keys[i]] = baseInstance[keys[i]];
}
// #endregion
};