I have a class where I want a simple factory method:
class GTree{
public static createNode(){
return new GNode();
}
}
This means that I don't want to allow the consumer to immediately instantiate the GNode.
How do I properly implement this?
Obviously I can't do:
class GNode{
constructor(){
throw TypeError("This is nonsense");
}
}
Because then I can't create nodes anymore at all.
How do I force using the factory?
Here's a simpler scheme than my earlier comments. Just define the GNode class in a private (but shared) scope and thus that's the only place the constructor can be called from and also reset the .constructor property so it doesn't leak out:
const GTree = (function() {
class GNode {
constructor() {
}
someOtherMethod() {
console.log("someOtherMethod");
}
}
// reset public .constructor
GNode.prototype.constructor = function() {
throw new Error("Can't call GNode constructor directly");
};
class GTree {
constructor() {
this.nodes = [];
}
createNode() {
let node = new GNode();
this.nodes.push(node);
return node;
}
get length() {
return this.nodes.length;
}
}
return GTree;
})();
let tree = new GTree();
let node1 = tree.createNode();
let node2 = tree.createNode();
node1.someOtherMethod();
console.log(tree.length + " nodes");
You can't really do that in javascript, but you can do this:
export class GTree {
public static createNode(name: string): GNode {
return new GNodeImpl(name);
}
}
export interface GNode {
name: string;
}
class GNodeImpl implements GNode {
constructor(public name: string) {}
}
(code in playground)
Only GTree and the GNode interface are exported, meaning that it's not possible to instantiate GNodeImpl from outside the module.
I added the name property just for the example.
Related
Im wondering if there is any way to pass a reference down to an injected object in Javascript. Cannot use inheritance in this case as this class will take many objects as parameters within its constructor that are created elsewhere.
class Parent {
private Child child;
constructor(child: Child) {
this.child = child;
}
// method needs to be called from within the `child`
public log(message: string) {
// logs out "Parent logged: This is a log message from the child"
console.log(`Parent logged: ${message}`);
}
}
class Child {
private Parent: parent;
constructor() { }
log() {
parent.log("This is a log message from the child");
}
}
If the object is built in the Parent constructor, you can just pass a reference in and then assign it to a property within Child. However, unfortunately the object is created outside the Parent class.
this.child = new Child(this);
It sounds like you just want a setter method on the child that allows you to set the parent, right?
class Child {
private Parent: parent;
constructor() { }
setParent(parent) {
this.parent = parent;
}
log() {
this.parent.log("This is a log message from the child");
}
}
Editing here, since I can't put code in the comment well. This is a much more complex example, but it's a well-defined pattern.
A similar example is the Datasource class in Apollo GraphQL, which has a base class that all of your "children" would implement, and you use that for cross-dependency with anything:
interface SomeDataSources {
someChild1: SomeChild1;
someChild2: SomeChild2;
someChild3: SomeChild3;
}
interface SomeContext {
datasources: SomeDataSources;
logger: Logger;
dbConnection: Connection;
}
class BaseDataSource {
context: SomeContext;
initialize({ context }: { context: SomeContext }) {
this.context = context;
}
}
class EachChildDoesThis extends BaseDataSource {
...
}
/**
* Instead of using a `Parent` class, this method does all of the
* dependency injection and closures necessary.
*/
const buildContext = async ({ logger, dbConnection }): Promise<SomeContext> => {
const context: SomeContext = {
logger,
dbConnection,
dataSources: {
someChild1: new SomeChild1(),
someChild2: new SomeChild2(),
someChild3: new SomeChild3(),
}
}
for (const dataSource of Object.values(context.dataSources)) {
await dataSource.initialize({
context,
});
}
return context;
};
Now you have an entire suite of functionality here that all have access both to the things you're passing in and to each other (dependency-injectedly) by doing this:
const logger = new Logger();
const dbConnection = createConnection(dbConfig);
const context = buildContext({ logger, dbConnection });
Any one of those child classes could do something like:
class SomeChild1 extends BaseDatasource {
async someMethod() {
this.context.logger.info('something something');
// or use the db
await this.context.dbConnection.doSomething();
// or even access its siblings
await this.context.dataSources.someChild2.delete('some-user-id');
}
}
Because of the dependency injection, each one of these is fully stand-alone, testable, and composable.
You could the following:
class Parent {
name: string;
age: number;
child = {};
constructor(name: string, age: number, child: Child) {
this.child = child;
this.name = name;
this.age = age;
}
static countParents() {
return `Parent count at: 10.`;
}
log(message: string) {
console.log(`Parent logged: ${message}.`);
}
}
class Child extends Parent {
name: string;
age: number;
favoriteHero: string
message: string
constructor(name, age, favoriteHero) {
super(name, age, favoriteHero);
this.favoriteHero = favoriteHero;
}
bio() {
return `${this.name} is 12 and his favorite super hero is ${this.favoriteHero}!`
}
}
const charlie = new Child('Charlie', 12, 'Batman')
charlie.log('Good things') // Parent logged: Good things.
console.log(charlie.bio()); // Charlie is 12 and his favorite super
hero is Batman!
console.log(Parent.countParents()); // Parent count at: 10
Here is a little background.
So I have my parent class. Constructor receives two objects: the first one is the class where the instance of the parent object was created (using others classes which are inheriting parent class).
The second object are just some parameters
parent.js
class ParentClass {
constructor(nativeObject, params) {
this.nativeObject = nativeObject;
this.param1 = params.param1;
this.param2 = params.param2;
}
// some logic here
}
module.exports = { ParentClass };
child.js
class ChildClass extends ParentClass {
// some logic here
}
module.exports = { ChildClass };
I'm using the child class in the other class methods for creating it. I can have multiple files like below:
usingclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(this, opts);
}
}
module.exports = { MyCoolClass };
anotherclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'thatAstringToo';
param2: 123;
}
class AnotherCoolClass{
alsoCreatesChild() {
return new ChildClass(this, opts);
}
}
module.exports = { AnotherCoolClass};
I can create multiple instances of the ChildClass in a different methods within one class:
thirdexample.js
let { ChildClass } = require('path/to/my/child/class');
let opts1 = {
param1: 'aStringAgain';
param2: 27;
}
let opts2 = {
param1: 'ABC';
param2: 33;
}
class ClassAgain{
firstMethod() {
return new ChildClass(this, opts1);
}
secondMethod() {
return new ChildClass(this, opts2);
}
}
module.exports = { ClassAgain };
The reason why I'm passing .this because I'm working with it in ParentClass
e.g for logging console.log(`I know this was created in ${this.nativeObject .constructor.name}`);
What I'm looking for is the way to get rid of passing .this every time I'm creating a new instance of the ChildClass
Reason behind this is that in one file (class) I can create up to 10+ instances of the ChildClass.
In the previous question I already got an answer which helped me to make the code more structured and more easy to maintain:
Instead of:
createChild() {
return new ChildClass(this, param1, param2, param3, paramN);
}
I'm doing:
createChild() {
return new ChildClass(this, paramObj);
}
So now I can store all my objects with parameters at the top of the file (of even separate file) and easily find and change it if needed.
...
Now I'm trying to find a way to get rid of .this every time I'm creating ChildClass
Is there any other way how ParentClass can know where does this ChildClass instances "lives"?
I'm trying to figure out can I do something using .apply or .call but seems like this is not a droids I'm looking for
UPDATE:
Since I have zero progress on this and I even got a comment that most likely this is not possible let's assume for now that I need a name of the class that is creating instance of the ChildClass only for logging.
How can I retrieve the name of the class without passing it every time when creating a ChildClass?
let opts = {
className: 'MyCoolClass'
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(opts);
}
}
module.exports = { MyCoolClass };
The problem is that I will have to set className: 'MyCoolClass' in each version of opts again and again and again... Just repeat the same for 10-15 times
You can just instantiate your classes using Reflect.construct.
class ParentClass {
constructor (nat, x) {
this.nat = nat
this.x = x
}
log () {
console.log('from ', this.nat.constructor.name, this.x)
}
}
class ChildClass extends ParentClass{}
class ClassAgain{
firstMethod () {
return this.makeInstance({ a: 1 })
}
secondMethod () {
return this.makeInstance({ b: 2 })
}
makeInstance (params) {
return Reflect.construct(ChildClass, [this, params])
}
}
const ca = new ClassAgain()
ca.firstMethod().log()
ca.secondMethod().log()
I've created a model in TypeScript that I'm using in a cast. When running the application, the model is not loaded and I'm unable to use any functions on that model.
Model
export class DataIDElement extends HTMLElement {
get dataID(): number {
var attributes: NamedNodeMap = this.attributes;
var dataIDAttribute: Attr = attributes.getNamedItem("data-id");
if (!dataIDAttribute) {
//throw error
}
var value: number = Number(dataIDAttribute.value);
return value;
}
}
Angular Component (Where model is being imported)
import { DataIDElement } from '../../models/dataIdElement';
export class PersonComponent
{
personClicked(event: KeyboardEvent): void {
var element: DataIDElement = <DataIDElement>event.target;
// This code always returns undefined (model isn't loaded)
var personID: number = element.dataID;
}
}
What you are doing there is a type assertion. That only overwrites the type inference of the compiler to make it believe that event.target is of the type DataIDElement. It doesn't create a new instance of DataIDElement.
If you want to create an instance of DataIDElement you need to create it using new.
DataIDElement would look something like this:
export class DataIDElement extends HTMLElement {
constructor(private target: HTMLElement) {}
get dataID(): number {
var attributes: NamedNodeMap = this.target.attributes;
var dataIDAttribute: Attr = attributes.getNamedItem("data-id");
if (!dataIDAttribute) {
//throw error
}
var value: number = Number(dataIDAttribute.value);
return value;
}
}
And would be used like this:
import { DataIDElement } from '../../models/dataIdElement';
export class PersonComponent
{
personClicked(event: KeyboardEvent): void {
var element: DataIDElement = new DataIDElement(event.target);
// This code always returns undefined (model isn't loaded)
var personID: number = element.dataID;
}
}
I have an inherited class, and need the parent to have a virtual method, which is overridden in the child class. This method is called from the base constructor, and needs access to instance properties, so it needs to be a lambda function, so "this" is "_this". The problem is, overriding a lambda method does not work for me like overriding a non-lambda does. Is this possible? If not, I'd like to understand why.
Also, will "this" always be the same as "_this" when the method is only called from the constructor?
class Base {
protected prop = null;
constructor() {
this.init();
this.initLambda();
}
init() {
console.log("Base init");
}
initLambda = () => {
console.log("Base initLambda");
}
}
class Derived extends Base {
constructor() {
super();
}
init() {
console.log("Derived init");
}
initLambda = () => {
//let x = this.prop;
console.log("Derived initLambda");
}
}
Output:
Derived init
Base initLambda
Well, you can't have that.
There's an issue that was opened but it was closed as "by design".
You should use regular methods:
class Base {
protected prop = null;
constructor() {
this.init();
this.initLambda();
}
init() {
console.log("Base init");
}
initLambda() {
console.log("Base initLambda");
}
}
class Derived extends Base {
constructor() {
super();
}
init() {
console.log("Derived init");
}
initLambda() {
console.log("Derived initLambda");
}
}
And then it will work.
As for keeping the right this, you can always pass a call to the method as an arrow function:
doit() {
setTimeout(() => this.init(), 1);
}
Or use the Function.prototype.bind function:
setTimeout(this.init.bind(this));
Also, the _this thing that the typescript compiler produces is just a hack to polyfil the arrow functions for ES5, but if you change the target to ES6 then it won't use it.
Edit:
You can save the bound methods as members:
class Base {
...
public boundInit: () => void;
constructor() {
...
this.boundInit = this.initLambda.bind(this);
setTimeout(this.boundInit, 500);
}
...
With that, when I do new Derived() this is what I get:
Derived init
Derived initLambda // after 200 millis
The problem is that your lambda is a property.
When compiled to javascript, the Baseclass becomes
var Base = (function () {
function Base() {
this.prop = null;
this.initLambda = function () {
console.log("Base initLambda");
};
this.init();
this.initLambda();
}
Base.prototype.init = function () {
console.log("Base init");
};
return Base;
}());
As you can see initLambda is defined inside the constructor of Base, so there is no way you can override that.
Calling super() calls the Base constructor which defines the this.initLambda with the code in Base and runs it. Hence your result.
View on playground
Try using using a getter property.
i.E.
get initLambda() {
return () => {
console.log("...");
}
}
In such way that the whole code looks like that:
class Base {
protected prop = null;
constructor() {
this.init();
this.initLambda();
}
init() {
console.log("Base init");
}
get initLambda() {
return () => {
console.log("Base initLambda");
}
}
}
class Derived extends Base {
constructor() {
super();
}
init() {
console.log("Derived init");
}
get initLambda() {
return () => {
//let x = this.prop;
console.log("Derived initLambda");
}
}
}
I am writing a simple ORM / wrapper for knex.js and there are two approaches I am trying to decide between.
The first approach is just a regular class which must be instantiated before use:
class Base {
constructor() {
this.db = db;
}
all() {
return this.db(this.table);
}
find(id) {
return this.db(this.table).where('id', id).first();
}
// more similar methods...
}
class User extends Base {
constructor() {
super();
this.table = 'users';
}
}
// calling code
let user = new User();
user.find(1).then(user => console.log(user));
The second approach, which I am more inclined to use, uses static methods which create the instance:
class Base {
constructor() {
this.db = db;
}
static all() {
const model = new this;
return model.db(model.table);
}
static find(id) {
const model = new this;
return model.db(model.table).where('id', id).first();
}
}
// calling code
User.find(1).then(user => console.log(user));
I like the static approach because the API is cleaner (no instantiating necessary) but I am not familiar with what the drawbacks are. So my questions are is this a good approach or not? And if not, why? Is there a third option that lies somewhere in the middle that would be better?
how about this if you wanna avoid objects?
class Base {
constructor() { }
static all() {
return db(this.table())
}
static find(id) {
return db(this.table()).where('id', id).first()
}
}
class User extends Base{
constructor() {
super()
}
static table(){
return 'users'
}
}
// calling code
User.find(1).then(user => console.log(user));
this would work, but I don't this is good way to do things, for starters, Base.all() would throw error, also if all your class methods are going to static, you might as well use plain js objects like:
let Base = {
all: function(){
return db(this.table)
},
find: function(id){
return db(this.table.where('id', id).first()
}
}
, extend = (a,b) => {for(let i in b) a[i] = b[i]}
, User = {table: 'users'}
// calling code
User.find(1).then(user => console.log(user));