I've heard about mixin pattern yesterday, and I'm trying it but I'm confused with what I think is a basic concept.
Let's focus on this function :
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
On the base constructor prototype iterations, one of the property name is constructor. So what we do is reassign the derivedClass prototype constructor to the last baseClass prototype constructor.
Let's say we have three class :
class Disposable {
public isDisposed: boolean;
public dispose() {
this.isDisposed = true;
}
}
class Activable {
public isActive: boolean;
public activate() {
this.isActive = true;
}
public deactivate(){
this.isActive = false;
}
}
class MyDisposableObject implements Disposable, Activable {
//Disposable
public isDisposed = false;
public dispose: () => void;
//Activable
public isActive = false;
public activate:() => void;
public deactivate:() => void;
}
After applyMixins(MyDisposableObject, [Disposable, Activable]) we no longer have the right constructor on MyDisposableObject instances.
const m = new MyDisposableObject();
console.log(m.constructor); // output : f Activable() {}
console.log(m instanceof MyDisposableObject) //output : true !?
According to MDN documentation, The constructor property returns a reference to the Object constructor function that created the instance object. which is not the case anymore because applyMixins reassign the derivedClass.prototype.constructor. Can't this lead to side-effect ?
var Disposable = /** #class */ (function () {
function Disposable() {
}
Disposable.prototype.dispose = function () {
this.isDisposed = true;
};
return Disposable;
}());
var Activable = /** #class */ (function () {
function Activable() {
}
Activable.prototype.activate = function () {
this.isActive = true;
};
Activable.prototype.deactivate = function () {
this.isActive = false;
};
return Activable;
}());
var MyDisposableObject = /** #class */ (function () {
function MyDisposableObject() {
//Disposable
this.isDisposed = false;
//Activable
this.isActive = false;
}
return MyDisposableObject;
}());
applyMixins(MyDisposableObject, [Disposable, Activable]);
function applyMixins(derived, bases) {
bases.forEach(function (base) {
Object.getOwnPropertyNames(base.prototype).forEach(function (name) {
derived.prototype[name] = base.prototype[name];
});
});
}
var m = new MyDisposableObject();
console.log('m constructor :', m.constructor);
console.log('m instanceof MyDisposableObject : ', m instanceof MyDisposableObject);
EDIT :
I still don't understand why the documentation example reassign the constructor of derivedClass.prototype and if this is harmful or not so while I wait for a great explanation, I modified the applyMixins to ignore constructor property :
function applyMixins(derived, bases) {
bases.forEach((base) => {
Object.getOwnPropertyNames(base.prototype)
.filter((name) => name.toLowerCase() !== 'constructor')
.forEach((name) => {
derived.prototype[name] = base.prototype[name];
})
})
}
Related
I am looking for a way how to replicate the following JavaScript functionality in TypeScript:
class Tree {
constructor(name) {
this.name = name;
this.type = 'unspecified';
}
grow() {
console.log(`${this.name} (${this.type}): growing.`);
}
}
class Pine extends Tree {
constructor(name) {
super(name);
this.type = 'pine';
}
somethinPiney() {
return true;
}
}
function treeMaker(treeType, name) {
return new treeType(name);
}
const t = new Tree('Noname');
const p = new Pine('Backyard');
const m = treeMaker(Pine, 'Seaside');
t.grow();
p.grow();
m.grow();
console.log(m instanceof Pine); // true
console.log(m instanceof Tree); // true
console.log(m instanceof Date); // false
More specifically, the treeType parameter type in the treeMaker function. It represents a new-able function. What syntax is used to describe such type? And how can I specify that I am interested only in Tree or a class that extends Tree?
Here you go with a TypeScript translation that's as close as possible to your JavaScript code:
class Tree {
constructor(private name: string, private type = 'unspecified') {
}
grow() {
console.log(`${this.name} (${this.type}): growing.`);
}
}
class Pine extends Tree {
constructor(name: string) {
super(name, 'pine');
}
somethinPiney() {
return true;
}
}
function treeMaker<T extends Tree>(treeType: new (name: string) => T, name: string): T {
return new treeType(name);
}
const t = new Tree('Noname');
const p = new Pine('Backyard');
const m = treeMaker(Pine, 'Seaside');
t.grow();
p.grow();
m.grow();
console.log(m instanceof Pine); // true
console.log(m instanceof Tree); // true
console.log(m instanceof Date); // false
Code Coverage doesn't reach some lines of codes even though I'm testing them. :(
Here is bind.ts decorator and [[NOT COVERED]] code coverage tag I created. Currently the set(value) is not covered by the test even though I'm covering it.
type Descriptor<T> = TypedPropertyDescriptor<T>;
export default function bind<T = Function>(
target: object,
key: string,
descriptor: Descriptor<T>
): Descriptor<T> {
...
set(value: T): void {
[[22-NOT COVERED]] if (process.env.NODE_ENV !== 'test') {
[[23-NOT COVERED]] throw new Error('Unable to set new value to decorated method');
[[24-NOT COVERED]] }
[[25-NOT COVERED]] Object.defineProperty(this, key, { ...descriptor, value });
},
};
}
bind.spec.ts
My strategy is to create new class Component and test its context on call
class MockClass extends React.PureComponent<Props, State> {
#bind
getProp(): string {
const { propName } = this.props;
return propName;
}
#bind
getState(): string {
const { stateName } = this.state;
return stateName;
}
#bind
setProp(value: string): void {
this.state = { stateName: value };
}
}
...
describe('bind', () => {
const mockState = {
stateName: 'stateName',
};
const mockProp = {
propName: 'propName',
};
const mockClass = new MockClass(mockProp, mockState);
...
it('should have called it once', () => {
expect(mockClass.getProp()).toBe(mockProp.propName);
});
it('should have called it in setState', () => {
expect(mockClass.setProp('newState')).toBe(undefined); <<<- This can cover 22-25??
});
The uncovered setter is code that would be exercised if you set a value of the class property. You don't have any test code that does this. You're only getting a property named setProp then calling it. The fact that the property has "set" in its name may be confusing matters.
Your test code would have to do something like this to test the setter of the decorator:
mockClass.props.otherPropName = 'blah';
mockClass.getProp = function() {
const { otherPropName } = this.props;
return otherPropName;
};
expect(mockClass.getProp()).toEqual('blah');
I am trying to create a class which looks like this:
class Foo {
_bar?: () => void; // this is a property which will invoke a function, if it is defined
set bar(barFunctionDef: () => void) { // this stores a reference to the function which we'll want to invoke
this._bar = barFunctionDef;
}
get bar() { // this should either invoke the function, or return a reference to the function so that it can be invoked
return this._bar;
}
doSomething() { // this is what will be called in an external class
if(condition) {
this.bar; // this is where I would like to invoke the function (bar)
}
}
}
Essentially, I want to have a class which will store references to functions which will be optionally set. There will be many "bar" class properties which will all have the type () => void.
Is there a "correct" way to call this.bar inside doSomething in order to invoke the function that is stored on that property?
Don't know if that's what you're trying to achieve, but I set up an example of generic implementation here
type MethodReturnsVoid = () => void;
class Foo {
methods: Map<string, MethodReturnsVoid>;
constructor() {
this.methods = new Map();
}
public getMethod(methodName: string): MethodReturnsVoid {
if (this.methods.has(methodName)) {
return this.methods.get(methodName);
}
return null;
}
public setMethod(methodName: string, method: () => void): void {
this.methods.set(methodName, method);
}
}
const foo = new Foo();
const func: MethodReturnsVoid = () => console.log('func');
const anotherFunc: MethodReturnsVoid = () => console.log('anotherFunc');
foo.setMethod('func', func);
foo.setMethod('anotherFunc', anotherFunc);
const methodFunc = foo.getMethod('func');
if (methodFunc) methodFunc();
const methodAnotherFunc = foo.getMethod('anotherFunc');
if (methodAnotherFunc) methodAnotherFunc();
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 cannot understand how 'this' context works in typescript. I cannot access class members in methods. Below is my code
class adopterDetailCtrl {
public adopter: IAdopter;
public $router: any;
static $inject = ['app.common.services.AdopterService'];
constructor(private adopterService: app.common.services.IAdopterServices) {
this.adopter = null;
}
$routerOnActivate(next) {
if (next.params.id > 0) {
this.getAdopterById(next.params.id);
}
}
getAdopterById(adopterId: number): void {
var AdopterList = this.adopterService.getAdopterById();
AdopterList.query({ id: adopterId }, (data: adopter.IAdopter[]) => {
this.adopter = data[0];//this.adopter is undefined here. this refers to 'window'
});
}
setAdopter(data: IAdopter) {
this.adopter = data;//can access this.adopter
}
}
The this context is just the same in typescript as it as in javascript, as the code you actually run is the compiled javascript that the typescript compiler outputs.
In javascript you have two ways to deal with this issue:
Use the arrow function
Use the Function.prototype.bind function
You are probably passing getAdopterById as a callback, if that's the case then it will be easy to solve using bind:
let myobj = new adopterDetailCtrl(...);
...
someFunction(myobj.getAdopterById.bind(myobj));
You can also modify the reference for the method of the instance in the ctor:
(1)
class adopterDetailCtrl {
public adopter: IAdopter;
public $router: any;
static $inject = ['app.common.services.AdopterService'];
constructor(private adopterService: app.common.services.IAdopterServices) {
this.adopter = null;
this.getAdopterById = (adopterId: number) => {
var AdopterList = this.adopterService.getAdopterById();
AdopterList.query({ id: adopterId }, (data: adopter.IAdopter[]) => {
this.adopter = data[0];//this.adopter is undefined here. this refers to 'window'
});
}
}
$routerOnActivate(next) {
if (next.params.id > 0) {
this.getAdopterById(next.params.id);
}
}
getAdopterById: (adopterId: number) => void;
setAdopter(data: IAdopter) {
this.adopter = data;//can access this.adopter
}
}
Notice that the method declaration is empty and the implementation is set in the ctor using the arrow function.
(2)
class adopterDetailCtrl {
public adopter: IAdopter;
public $router: any;
static $inject = ['app.common.services.AdopterService'];
constructor(private adopterService: app.common.services.IAdopterServices) {
this.adopter = null;
this.getAdopterById = this.getAdopterById.bind(this);
}
$routerOnActivate(next) {
if (next.params.id > 0) {
this.getAdopterById(next.params.id);
}
}
getAdopterById(adopterId: number): void {
var AdopterList = this.adopterService.getAdopterById();
AdopterList.query({ id: adopterId }, (data: adopter.IAdopter[]) => {
this.adopter = data[0];//this.adopter is undefined here. this refers to 'window'
});
}
setAdopter(data: IAdopter) {
this.adopter = data;//can access this.adopter
}
}
Here in the ctor you reassign the bound this.getAdopterById.bind(this) to this.getAdopterById.
In both of these cases you can freely pass the getAdopterById method as a callback and not worrying about the scope of this.
Another note on the arrow functions is that this is a new feature in ES6, and if you don't choose the ES6 target in your compilation options then the compiler won't actually use this notation but instead will convert this:
class A {
private x: number;
fn(): void {
setTimeout(() => console.log(this.x), 1);
}
}
To:
var A = (function () {
function A() {
}
A.prototype.fn = function () {
var _this = this;
setTimeout(function () { return console.log(_this.x); }, 1);
};
return A;
}());
This way the scope of this is saved in _this and in the callback function _this.x is used instead of this.x.