There is a way in JS to execute a default method when I call a instance?
Example: Let's assume I have the following class named MyClass, then I start a instance of this class named foo, I want that when I call foo execute the default method of MyClass.
class MyClass {
constructor () {
this.props = {
question: 'live, universe, everything',
answer: 42
}
}
default () {
return this.props
}
hello () {
return 'hello world'
}
}
const foo = new MyClass()
// execute the default method
console.log(foo) // log: {question: 'live, universe, everything', answer: 42}
// execute hello method
console.log(foo.hello()) // log: hello world
The only default method that is called when instantiating an object is the constructor.
In ES6 you can return whatever you want from the constructor so the following code is valid:
class MyClass {
constructor () {
var instance = {
question: 'live, universe, everything',
answer: 42,
hello: () => { return 'hello world' }
}
return instance;
}
}
You can then instantiate an object like this:
var foo = new MyClass();
foo.hello(); //Hello world
console.log(foo.question); //live, universe, everything
console.log(foo.answer); //42
Related
I have a class in which i use strategy pattern it looks something like this:
class Foo {
constructor() {
this.doStuff = function() { /* not used really */ }
}
create(config) {
this.type = config.type;
// assign other variables to this
this.doStuff = StuffStrategy(config.type);
this.doStuff();
}
}
StuffStrategy is a function that returns other functions which use this context differently based on type.
function StuffStrategy(type) {
switch(type) {
case A: return StrategyA;
case B: return StrategyB;
// ...
}
}
function StrategyA() {
if(this.someVarInFoo) {
return 'a thing'
} else
return 'a different thing' + this.otherVarInFoo
}
I assign particular Strategy function inside create method.
Then I would like to test the create method if it calls doStuff.
describe('how create method works', () => {
const instance = new Foo();
const spy = jest.spyOn(instance, 'doStuff');
instance.create(config);
expect(spy).toBeCalled();
});
But when I try to make spy before calling instance.create then it refers to default method assigned in constructor, which gets replaced inside create.
If i make spy after calling instance.create then it will not pick the call
.
I tried to add .bind when defining this.doStuff:
this.doStuff = StuffStrategy(config.type).bind(this);
but it does not work either.
Is there something wrong with my setup?
How can I make this test case work?
You have to spyOn the strategy methods of your Foo class. So for every config.type you check then which strategy method has been called.
export class Foo {
constructor(){
this.doStuff = null;
}
create(config){
this.type = config.type;
// assign other variables to this
this.doStuff = StuffStrategy(config.type);
this.doStuff();
}
strategyA(){...}
strategyB(){...}
StuffStrategy(configtype) {
switch (configtype) {
case "A": return this.strategyA;
case "B": return this.strategyB;
}
}
}
import { Foo } from 'anyPlaceFoo/foo';
describe('Strategy', () => {
it('should call strategy A', () => {
const foo = new Foo();
// here you can spy on every strategy method.
jest.spyOn(foo, 'strategyA');
jest.spyOn(foo, 'strategyB');
foo.create({ type: 'A' });
// check if the selected one has been called but not the others
expect(foo.strategyA).toHaveBeenCalled();
expect(foo.strategyB).not.toHaveBeenCalled();
})
})
I want to keep this in class methods.
I can use arrow functions, but I want to override some methods in extended class.
Now I have this solution and it works:
class Foo {
bar = "Context preserved.";
constructor() {
this.foo = this.foo.bind(this);
}
foo() {
alert(this.bar);
}
}
class Foo2 extends Foo {
foo() {
alert(this.bar + " Class extended");
}
}
class Bar {
bar = "Context lost.";
}
let foo = new Foo2();
let bar = new Bar();
foo.foo.apply(bar); // Context preserved. Class extended
Is it a good practice to do it such way? If it is, is there some keyword in typescript to do it automatically?
like
class Foo() {
public conserved foo() { }
}
which generates:
var Foo = (function () {
function Foo() {
this.foo = this.foo.bind(this);
}
Foo.prototype.foo = function () { };
return Foo;
}());
It's a valid practice and it's being used.
I'm unaware of a way to tell typescript to do this automatically, but you can search the issues for something like it.
You can have a decorator that does that for you, for example:
function construct(constructor: Function, methods: string[], args: any[]) {
var c: any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
let instance = new c();
methods.forEach(name => {
instance[name] = instance[name].bind(instance);
});
return instance;
}
function BindMethods(constructor: Function) {
const methods = [] as string[];
Object.keys(constructor.prototype).forEach(name => {
if (typeof constructor.prototype[name] === "function") {
methods.push(name);
}
});
return (...args: any[]) => {
return construct(constructor, methods, args);
};
}
#BindMethods
class Foo {
bar = "Context preserved.";
foo() {
console.log(this.bar);
}
}
let foo = new Foo();
setTimeout(foo.foo, 10);
(code in playground)
I tested it with this simple use case and it worked just fine.
I have an interface/type defined and I don't want to redefine it everywhere I want to use the mixin. Think facebook's mixins: [] property
Example usecase:
const mixin = root => Object.assign(root, {bar: () => 'baz'));
class Foo {
constructor () { mixin(this); }
test () { console.log(this.bar()) } // <-- this gives an error
}
From what I could find online, this is what i've tried:
interface MyMyxin {
bar () : string;
}
function mixin <T:Object> (root: T) : T & MyMyxin {
return Object.assign(root, {bar: () => 'baz'});
}
class Foo {
constructor () {
mixin(this);
}
test () {
console.log(this.bar()); // <-- no luck
}
}
mixin(Foo.prototype) // <--- this also does not work
But I can't get flow to understand that the mixin method adds an extra property to the object
In the following example, how can I directly call Base.foo() from Base.bar(), I understand why it's calling Test.foo() first, but is there any way to prevent this.
class Base {
constructor() {
console.log('Base constructor')
}
foo() {
console.log('Base foo')
}
bar() {
console.log('Base bar')
this.foo();
}
}
class Test extends Base {
foo() {
console.log('Test foo');
super.foo();
}
bar() {
console.log('Test bar');
super.bar();
}
}
const test = new Test();
test.bar();
Output would be:
Base constructor
Test bar
Base bar
Test foo
Base foo
My expected output would be:
Base constructor
Test bar
Base bar
Base foo
You can use standard JavaScript features while using ES2015 classes. In this case, simply use Function.prototype.call():
'use strict'
class Base {
constructor() {
console.log('Base constructor')
}
foo() {
console.log('Base foo')
}
bar() {
console.log('Base bar')
Base.prototype.foo.call(this);
}
}
class Test extends Base {
foo() {
console.log('Test foo');
super.foo();
}
bar() {
console.log('Test bar');
super.bar();
}
}
const test = new Test();
test.bar();
Is there a way to nest classes in TypeScript. E.g. I'd like to use them like:
var foo = new Foo();
var bar = new Foo.Bar();
In modern TypeScript we have class expressions which you can use to create a nested class. For example you can do the following :
class Foo {
static Bar = class {
}
}
// works!
var foo = new Foo();
var bar = new Foo.Bar();
Here is a more complex use case using class expressions.
It allows the inner class to access the private members of the outer class.
class classX {
private y: number = 0;
public getY(): number { return this.y; }
public utilities = new class {
constructor(public superThis: classX) {
}
public testSetOuterPrivate(target: number) {
this.superThis.y = target;
}
}(this);
}
const x1: classX = new classX();
alert(x1.getY());
x1.utilities.testSetOuterPrivate(4);
alert(x1.getY());
codepen
I couldn't get this to work with exported classes without receiving a compile error, instead I used namespaces:
namespace MyNamespace {
export class Foo { }
}
namespace MyNamespace.Foo {
export class Bar { }
}
If you're in the context of a type declaration file, you can do this by mixing classes and namespaces:
// foo.d.ts
declare class Foo {
constructor();
fooMethod(): any;
}
declare namespace Foo {
class Bar {
constructor();
barMethod(): any;
}
}
// ...elsewhere
const foo = new Foo();
const bar = new Foo.Bar();
This answer is about a seemless nested class implementation in TypeScript which builds on top of #basarat 's answer.
To make the type of the static nested class Bar accessible (as #PeterMoore pointed out), declare the type of the nested class in a namespace. That way, we can use the shortcut Foo.Bar. By moving type typeof Foo.Bar.prototype into a type in a declared namespace, we do not have to repeat the expression.
class Foo {
static Bar = class {
}
}
declare namespace Foo {
type Bar = typeof Foo.Bar.prototype
}
// Now we are able to use `Foo.Bar` as a type
let bar: Foo.Bar = new Foo.Bar()
For static classes, the following implementation might be more elegant. This, however does not work with non-static classes.
class Foo { }
namespace Foo {
export class Bar { }
}
let bar: Foo.Bar = new Foo.Bar()
To export the class, an export statement can be added after the class and the namespace have been declared, e.g. export default Foo or export { Foo }.
To achieve the same with a non-static nested class, see the following example.
class Foo {
Bar = class {
}
}
declare namespace Foo.prototype {
type Bar = typeof Foo.prototype.Bar.prototype
}
let foo: Foo = new Foo()
let bar: Foo.prototype.Bar = new foo.Bar()
I Hope this can be helpful
Able to:
Create a new inner class instance
Access outer class instance/prototype members
Implement interfaces
Use decorators
Use Case
export interface Constructor<T> {
new(...args: any[]): T;
}
export interface Testable {
test(): void;
}
export function LogClassName<T>() {
return function (target: Constructor<T>) {
console.log(target.name);
}
}
class OuterClass {
private _prop1: string;
constructor(prop1: string) {
this._prop1 = prop1;
}
private method1(): string {
return 'private outer method 1';
}
public InnerClass = (
() => {
const $outer = this;
#LogClassName()
class InnerClass implements Testable {
private readonly _$outer: typeof $outer;
constructor(public innerProp1: string) {
this._$outer = $outer;
}
public test(): void {
console.log('test()');
}
public outerPrivateProp1(): string {
return this._$outer._prop1;
}
public outerPrivateMethod1(): string {
return this._$outer.method1();
}
}
return InnerClass;
}
)();
}
const outer = new OuterClass('outer prop 1')
const inner = new outer.InnerClass('inner prop 1');
console.log(inner instanceof outer.InnerClass); // true
console.log(inner.innerProp1); // inner prop 1
console.log(inner.outerPrivateProp1()); // outer prop 1
console.log(inner.outerPrivateMethod1()); // private outer method 1