GithubHelp home page GithubHelp logo

fasttime / polytype Goto Github PK

View Code? Open in Web Editor NEW
60.0 7.0 3.0 564 KB

Dynamic multiple inheritance for JavaScript and TypeScript. Without mixins.

License: ISC License

JavaScript 94.63% TypeScript 2.39% Handlebars 2.98%
class classes inheritance multiple-inheritance es2020

polytype's Introduction

Polytype · npm version

Dynamic multiple inheritance for JavaScript and TypeScript. Without mixins.

Polytype is a library that adds support for dynamic multiple inheritance to JavaScript and TypeScript with a simple syntax. “Dynamic” means that changes to base classes at runtime are reflected immediately in all derived classes just as programmers would expect when working with single prototype inheritance.

Polytype runs in Node.js, Deno and in current versions of all major browsers.

Contents

Features

  • Python style multiple inheritance
  • Works in Node.js, Deno and in all new browsers
  • Full TypeScript support
  • Zero dependencies
  • Access to all inherited base class features
    • constructors
    • methods, getters and setters, class fields
    • value properties on base classes and base instance prototypes
  • in, instanceof and isPrototypeOf integration

Setup Instructions

Polytytpe is available in two flavors: a module build (comprising CommonJS and ECMAScript modules) with exported definitions and a script build where all definitions are accessible through global objects. Apart from this, both builds provide the same features and are available in the standard package.

In Node.js

If you are using Node.js, you can install Polytype with npm.

npm install polytype

Then you can import it in your code like any module.

const { classes } = require("polytype"); // CommonJS syntax

or

import { classes } from "polytype"; // ECMAScript module syntax

Alternatively, you can import the script build at the start of your application and access Polytype definitions through global objects.

require("polytype/global"); // CommonJS syntax

or

import "polytype/global"; // ECMAScript module syntax

In Deno

You can import the module or script build of Polytype from a CDN of your choice, e.g.

import { classes } from "https://esm.sh/polytype"; // Module build

or

import "https://esm.sh/polytype/global"; // Script build

In the browser

In an HTML‐based application, the script build of Polytype can be simply embedded. Just download polytype.min.js and include it in your HTML file.

<script src="polytype.min.js"></script>

Alternatively, you can hotlink the script from the latest release package using a CDN of your choice.

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/polytype.min.js"></script>

If your browser application already uses ECMAScript modules, you can also import the module build (“.mjs”) in contexts where Polytype specific definitions like classes are required. This has the advantage to avoid possible naming conflicts on global objects.

import { classes } from "https://cdn.jsdelivr.net/npm/[email protected]/lib/polytype.min.mjs";

Usage

Inheriting from multiple base classes

For example, declare a derived class ColoredCircle that inherits from both base classes Circle and ColoredObject.

class Circle
{
    constructor(centerX, centerY, radius = 1)
    {
        this.moveTo(centerX, centerY);
        this.radius = radius;
    }
    get diameter() { return this.radius * 2; }
    set diameter(diameter) { this.radius = diameter / 2; }
    moveTo(centerX, centerY)
    {
        this.centerX = centerX;
        this.centerY = centerY;
    }
    reset()
    {
        this.moveTo(0, 0);
        this.radius = 1;
    }
    toString()
    {
        return `circle with center (${this.centerX}, ${this.centerY}) and radius ${this.radius}`;
    }
}

class ColoredObject
{
    constructor(color) { this.color = color; }
    static areSameColor(obj1, obj2) { return obj1.color === obj2.color; }
    paint() { console.log(`painting in ${this.color}`); }
    reset() { this.color = "white"; }
    toString() { return `${this.color} object`; }
}

class ColoredCircle
extends classes(Circle, ColoredObject) // Base classes as comma‐separated params
{
    // Add methods here.
}

Using methods and properties from multiple base classes

const c = new ColoredCircle();

c.moveTo(42, 31);
c.radius = 2;
c.color = "red";
console.log(c.centerX, c.centerY);  // 42, 31
console.log(c.diameter);            // 4
c.paint();                          // "painting in red"

As usual, the keyword super invokes a base class method or property accessor when used inside a derived class.

class ColoredCircle
extends classes(Circle, ColoredObject)
{
    paint()
    {
        super.paint(); // Using method paint from some base class
    }
}

If different base classes include a member with the same name, the syntax

super.class(DirectBaseClass).member

can be used to make the member access unambiguous.

class ColoredCircle
extends classes(Circle, ColoredObject)
{
    toString()
    {
        // Using method toString from base class Circle
        const circleString = super.class(Circle).toString();
        return `${circleString} in ${this.color}`;
    }
}

More generally, super.class(DirectBaseClass)[propertyKey] can be used to reference a (possibly inherited) property of a particular direct base class in the body of a derived class.

Note: In TypeScript, the syntax described here cannot be used to access protected instance members, so it is currently not possible to disambiguate between protected instance members having the same name, the same index or the same symbol in different base classes.

Static methods and properties

Static methods and property accessors are inherited, too.

ColoredCircle.areSameColor(c1, c2)

same as

ColoredObject.areSameColor(c1, c2)

Invoking multiple base constructors

In the constructor of a derived class, use arrays to group together parameters to be passed to the constructors of each direct base class.

class ColoredCircle
extends classes(Circle, ColoredObject)
{
    constructor(centerX, centerY, radius, color)
    {
        super
        (
            [centerX, centerY, radius], // Circle constructor params
            [color]                     // ColoredObject constructor params
        );
    }
}

If you prefer to keep parameter lists associated to their base classes explicitly without relying on order, there is an alternative syntax.

class GreenCircle
extends classes(Circle, ColoredObject)
{
    constructor(centerX, centerY, radius)
    {
        super
        (
            { super: ColoredObject, arguments: ["green"] },
            { super: Circle, arguments: [centerX, centerY, radius] }
        );
    }
}

There is no need to specify an array of parameters for each direct base constructor. If the parameter arrays are omitted, the base constructors will still be invoked without parameters.

class WhiteUnitCircle
extends classes(Circle, ColoredObject)
{
    constructor()
    {
        super(); // Base constructors invoked without parameters
        this.centerX    = 0;
        this.centerY    = 0;
        // The radius has been already set to 1 by the Circle constructor.
        this.color      = "white";
    }
}

instanceof

The instanceof operator works just as it should.

const c = new ColoredCircle();

console.log(c instanceof Circle);           // true
console.log(c instanceof ColoredObject);    // true
console.log(c instanceof ColoredCircle);    // true
console.log(c instanceof Object);           // true
console.log(c instanceof Array);            // false

In pure JavaScript, the expression

B.prototype instanceof A

determines if A is a base class of class B.

Polytype takes care that this test still works well with multiple inheritance.

console.log(ColoredCircle.prototype instanceof Circle);         // true
console.log(ColoredCircle.prototype instanceof ColoredObject);  // true
console.log(ColoredCircle.prototype instanceof ColoredCircle);  // false
console.log(ColoredCircle.prototype instanceof Object);         // true
console.log(Circle.prototype instanceof ColoredObject);         // false

in

The in operator determines whether a property is in an object or in its prototype chain. In the case of multiple inheritance, the prototype “chain” looks more like a directed graph, yet the function of the in operator is the same.

const c = new ColoredCircle();

console.log("moveTo" in c); // true
console.log("paint" in c);  // true
console.log("areSameColor" in ColoredCircle);   // true
console.log("areSameColor" in Circle);          // false
console.log("areSameColor" in ColoredObject);   // true

isPrototypeOf

isPrototypeOf works fine, too.

const c = new ColoredCircle();

console.log(Circle.prototype.isPrototypeOf(c));         // true
console.log(ColoredObject.prototype.isPrototypeOf(c));  // true
console.log(ColoredCircle.prototype.isPrototypeOf(c));  // true
console.log(Object.prototype.isPrototypeOf(c));         // true
console.log(Array.prototype.isPrototypeOf(c));          // false
console.log(Circle.isPrototypeOf(ColoredCircle));               // true
console.log(ColoredObject.isPrototypeOf(ColoredCircle));        // true
console.log(ColoredCircle.isPrototypeOf(ColoredCircle));        // false
console.log(Object.isPrototypeOf(ColoredCircle));               // false
console.log(Function.prototype.isPrototypeOf(ColoredCircle));   // true

Finding the base classes

In single inheritance JavaScript, the direct base class of a derived class is obtained with Object.getPrototypeOf.

const DirectBaseClass = Object.getPrototypeOf(DerivedClass);

If a class has no explicit extends clause, Object.getPrototypeOf returns Function.prototype, the ancestor of all classes.

Of course this method cannot work with multiple inheritance, since there is no way to return multiple classes without packing them in some kind of structure. For this and other use cases, Polytype exports the function getPrototypeListOf, which can be used to get an array of direct base classes given a derived class.

const { getPrototypeListOf } = require("polytype"); // Or some other kind of import.

function getBaseNames(derivedClass)
{
    return getPrototypeListOf(derivedClass).map(({ name }) => name);
}

console.log(getBaseNames(ColoredCircle));   // ["Circle", "ColoredObject"]
console.log(getBaseNames(Int8Array));       // ["TypedArray"]
console.log(getBaseNames(Circle));          // [""] i.e. [Function.prototype.name]

When the the script build of Polytype is used, no functions will be exported. Instead, getPrototypeListOf will be defined globally as Object.getPrototypeListOf.

Dispatching invocations to multiple base classes

Sometimes it is useful to have a method or setter invocation dispatched to all direct base classes rather than just to one of them. Common examples are event handlers and Angular lifecycle hooks implemented in multiple base classes.

Polytype has no dedicated syntax for this use case: simply override the method or setter in the derived class and invoke the base implementations from there.

class ColoredCircle
extends classes(Circle, ColoredObject)
{
    reset()
    {
        super.class(Circle).reset();
        super.class(ColoredObject).reset();
    }
}

This can also be done with an iteration instead of referencing the base classes one by one.

class ColoredCircle
extends classes(Circle, ColoredObject)
{
    reset()
    {
        for (const baseClass of getPrototypeListOf(ColoredCircle))
            baseClass.reset();
    }
}

Dynamic base class changes

If a property in a base class is added, removed or modified at runtime, the changes are immediately reflected in all derived classes.

const c = new ColoredCircle();

Circle.prototype.sayHello = () => console.log("Hello!");
c.sayHello(); // "Hello!"

TypeScript support

Polytype has built‐in TypeScript support: you can take advantage of type checking while working with multiple inheritance without installing any additional packages. If you are using an IDE that supports TypeScript code completion like Visual Studio Code, you will get multiple inheritance sensitive suggestions as you type. A TypeScript version of the ColoredCircle sample code above can be found in ColoredCircle.ts in the example folder.

Caveats

Neither JavaScript nor TypeScript offer native support for multiple inheritance of any kind. Polytype strives to make up for this deficiency, but some important limitations remain.

this in base constructors

In single inheritance, the value of this inside a constructor and in the constructors of all ancestor classes is the same object that the new operator returns. Not so in Polytype multiple inheritance, where the value of this inside base constructors is a special object called a substitute. Substitutes are necessary to make base constructors run independently from each other, each one with its own fresh instance. Only after all base constructors of a derived class have run, the properties of the substitues are merged into one object.

let aThis, bThis, cThis;

class A
{
    constructor()
    {
        aThis = this;
    }
}

class B
{
    constructor()
    {
        bThis = this;
    }
}

class C extends classes(A, B)
{
    constructor()
    {
        super();
        cThis = this;
    }
}

const obj = new C();
console.log(aThis === obj);     // Prints false.
console.log(bThis === obj);     // Prints false.
console.log(aThis === bThis);   // Prints false.
console.log(cThis === obj);     // Prints true.

After the base constructors have run, the substitutes become detached, meaning that they no longer reflect changes to the real instance and vice versa. This may cause problems with classes that store or bind the value of this in the constructor to use it later.

For example, the following code that attaches a click handler to an HTML button will not work as one might expect, because this in the event handler refers to a substitute that is unaware of the name property later assigned to the instance of the derived class.

class A
{
  constructor()
  {
    button.onclick = () => alert(this.name);
  }
}

class B
{ }

class C extends classes(A, B)
{ }

const c = new C();
c.name = 'Test';
button.click(); // Alerts "undefined" rather than "Test".

While Polytype takes some actions to mitigate the effect of detached substitutes, like retargeting bound methods if necessary, classes that access the value of this in the constructor in order to use it later are generally not safe to subclass.

Base classes with private instance members

Polytype classes do not inherit private members declared in their base classes. For this reason, extending base classes with private instance members will not work well and likely result in TypeErrors at runtime.

for...in iterations

When only single inheritance is used, a for...in iteration over a class constructor enumerates not only names of enumerable properties defined on the constructor object itself, but also names of enumerable properties defined on all base constructors in its prototype chain. Enumerable properties on class constructors can be defined with a static field, or assigned dynamically.

class FooBarClass
{
    static foo = "foo";
}

FooBarClass.bar = "bar";

class BazClass extends FooBarClass
{
    static baz = "baz";
}

for (const name in BazClass)
    console.log(name); // Prints "baz", "foo" and "bar".

As it happens, this behavior no longer holds with Polytype multiple inheritance. The effect is that names of static fields and other enumerable properties defined on a base constructor are not enumerated by for...in statements when the inheritance line crosses a class listed in some extends classes(...) clause.

class BazClass extends classes(FooBarClass)
{
    static baz = "baz";
}

for (const name in BazClass)
    console.log(name); // Prints just "baz".

For this reason, and because generally better alternatives exist, iterating over Polytype classes and their derived classes with for...in is not recommended.

Member resolution order

Multiple base classes may expose members with the same name, the same index or the same symbol. When this happens, any unqualified access to one of those members will have to determine the implementation to be used. The approach taken by Polytype is to pick the implementation found in the first direct base class that contains the (possibly inherited) member.

class A
{ }

class B
{
    whoAmI() { console.log("B"); }
}

class C
{
    whoAmI() { console.log("C"); }
}

class ABC extends classes(A, B, C)
{ }

const abc = new ABC();
abc.whoAmI(); // Prints "B".

This is similar to the depth‐first search algorithm of old‐style classes in Python 2, but it is different from the behavior of several other programming languages that support multiple inheritance, and it may not match your expectations if you come from a C++ background.

Ambiguous protected instance members

When a derived class inherits from multiple base classes, it is possible for inherited members in different base classes to share the same property key, i.e. the same name, the same index or the same symbol. For these cases, Polytype provides the syntax super.class(DirectBaseClass)[propertyKey] to specify the direct base class containing the member to be accessed. This works all the time in JavaScript and works in TypeScript for any public or static member, but results in a compiler error when applied to a protected instance member.

class RecordLeft
{
    protected id: number;
}

class RecordRight
{
    protected id: string;
}

class Record extends classes(RecordLeft, RecordRight)
{
    public printRightId(): void
    {
        // error TS2446: Property 'id' is protected…
        console.log(super.class(RecordRight).id.padStart(10, ' '));
    }
}

As a type‐safe workaround, use an intermediate class to expose the inherited member using a different name without making it public.

class RecordRightProxy extends RecordRight
{
    protected get rightId(): string
    {
        return super.id;
    }
}

class Record extends classes(RecordLeft, RecordRightProxy)
{
    public printRightId(): void
    {
        console.log(super.rightId.padStart(10, ' ')); // OK
    }
}

Compatibility

Polytype was successfully tested in the following browsers/JavaScript engines.

Chrome Chrome 80+
Safari Safari 14+
Edge Edge 80+
Firefox Firefox 74+
Opera Opera 67+
Node.js Node.js 16+
Deno Deno 1.24+

The minimum supported TypeScript version is 4.7.

Bundlers and other tools that process uncompressed Polytype source files are required to parse ECMAScript 2020 or higher syntax.

polytype's People

Contributors

fasttime avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

polytype's Issues

Issue with ngOnDestry method when use polytype

I have a component that navigate to other page when finish the method ngOnInit:

import { Component, OnInit } from '@angular/core';
import { ClassA } from './classA';
import { ClassB } from './classB';
import { classes } from 'polytype';
import { ClassAService} from './classA.service';
import { ClassBService} from './classB.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.sass']
})
export class AppComponent extends classes(ClassA, ClassB)  {

  constructor(
    classAService: ClassAService,
    classBService: ClassBService
) {
    super(['classAService'], ['classBService']);
  }

  ngOnInit(): void {
    // Do something and navigate to other page
  }
}

The issue come when both extended classes have ngOnDestroy method, only the first class execute ngOnDestroy and should both classes execute It. I need to do the following to make it works correctly but I think It is not the best option:

 ngOnInit(): void {
    // Navigate to other page
   super.class(ClassB).ngOnDestroy();
  }

Thank you for your great work I look forward to your answer.

polytype extended class properties not behaving as typescript extended class propeties

Hi,
I am trying to use polytype in my angular9/typescript project

Consider the following code for single class inheritance in typescript:

export class AbstractBaseComponent{
form: FormGroup
}


@Component ({
   selector: 'pim'
   template: `
  <form [formGroup] = 'form'>

   </form>
`

})
export class PimComponent extends AbstractBaseComponent {
// the form of the template is referenced by the form of the Abstract
BaseComponent as expected

}

For polytype multi-inheritance

@Component ({
   selector: 'pim'
   template: `
  <form [formGroup] = 'form'>

   </form>
`

})
export class PimComponent extends classes(AbstractBaseComponent) {
// the form of the template is NOT referenced by the form of the AbstractBaseComponent as expected

}

What am I doing incorrectly. My Webstorm IDE flag the template form as error (Is this IDE failure or polytype failure?)

Thanks for your help

Abstract Class Support

Hey

I was trying to use this library to do something a tad bit weird and ran into an error.

Cannot assign an abstract constructor type to a non-abstract constructor type

To recreate the bug

abstract class AbstractClass {}

class Test extends classes(AbstractClass) {}
// Argument of type 'typeof AbstractClass' is not assignable to parameter of type 'SuperConstructor'.
//  Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2345)

Not sure if this is just a typing bug, ie the package supports abstract classes but the types have to be updated or if they package itself doesn't have abstract class support

explicit classes export

Hi

In general manipulating global isn't a good idea (in some cases it does have value, so won't argue that) and it would be safer if we can just

// explicit usage
import { classes } from 'polytype';

// global usage
import defineGlobally from 'polytype';
defineGlobally();
// or 
import 'polytype/global';

Improving typing for getPrototypeListOf

I was using super.class(BaseClass) in may cases and it handles typing correctly.
I see: protected class<U extends T>(type: U): U; in the .d.ts file

I was trying out getPrototypeListOf and realized that I was getting all the baseClass as any
And I saw: export function getPrototypeListOf(o: any): any[]; in the .d.ts file
So, creating this issue to potentially improve the typing here to get more accurate types

PS: I have no idea if it is possible at all :)

I did try this:

export class ChildClass extends classes(BaseClass1, BaseClass2) {
    myFunc() {
        (getPrototypeListOf(ChildClass) as (BaseClass1 | BaseClass2)[]).forEach(baseClass => {
            baseClass.myFunc()
        })
    }
}

NOTE: This will assume baseClass has the type (typeof BaseClass1 | typeof BaseClass2)
So, if all my base classes implement a particular function - then this work out quite well

Doing: getPrototypeListOf(ChildClass) as [BaseClass1, BaseClass2] would also work and be more accurate

core.js:4117 ERROR TypeError: Constructor cannot be invoked without 'new'

Hi,
I have created a simple angular-9 component and attempt to use polytype to extend a typescript Utils class.

I am using the following packages:

  • "@angular/cli": "^9.1.7"
  • "typescript": "3.8.3"
  • "polytype": "^0.9.4",
import {  Component,  OnInit,} from '@angular/core'
import { NgControl } from '@angular/forms'
import { classes } from 'polytype'

export class Utils {
//  constructor() {} - does not work either if uncommented
  
  onFocusInShowPendingValidationStatus( validationStatus: string, evernt?: Event ): string {
    return validationStatus = 'pending'
  }
  
  onFocusOutValidationStatus( validationStatus: string, control: NgControl, event?: Event ): string {
    return validationStatus = control.valid ? 'valid' : 'invalid'
  }
  
  onValueChangedValidationStatus( validationStatus: string, control: NgControl, event? ): string {
    return validationStatus = control.valid ? 'valid' : 'invalid'
  }
}

@Component(
    {
      selector: 'pim-poly-types',
      template: `
        <p>
          poly-types works!
        </p>
      `,
      styles: [],
    } )
export class PolyTypesComponent extends classes( Utils ) implements OnInit {
  
  constructor() {super() }
  
  ngOnInit(): void {
  }
}

However, I am consistently getting the following error that prevents the app from running. The app does compile though.

core.js:4117 ERROR TypeError: Constructor cannot be invoked without 'new' at Object.apply (polytype.mjs:43) at new PolyTypesComponent (poly-types.component.ts:36) at NodeInjectorFactory.PolyTypesComponent_Factory [as factory] (poly-types.component.ts:39) at getNodeInjectable (core.js:3956) at instantiateAllDirectives (core.js:8407) at createDirectivesInstances (core.js:7774) at ɵɵelementStart (core.js:14518) at Module.ɵɵelement (core.js:14569) at LoginFormComponent_Template (login-form.component.html:85) at executeTemplate (core.js:7747) defaultErrorLogger @ core.js:4117 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.ErrorHandler.handleError @ core.js:4165 (anonymous) @ core.js:29098 push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:386 push.../../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:143 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.NgZone.runOutsideAngular @ core.js:28074 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.ApplicationRef.tick @ core.js:29098 (anonymous) @ core.js:28954 push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:386 onInvoke @ core.js:28134 push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:385 push.../../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:143 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.NgZone.run @ core.js:28029 next @ core.js:28953 schedulerFn @ core.js:25506 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub @ Subscriber.js:192 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next @ Subscriber.js:130 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next @ Subscriber.js:76 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next @ Subscriber.js:53 push.../../node_modules/rxjs/_esm5/internal/Subject.js.Subject.next @ Subject.js:47 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.EventEmitter.emit @ core.js:25488 checkStable @ core.js:28084 onHasTask @ core.js:28148 push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.hasTask @ zone.js:441 push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate._updateTaskCount @ zone.js:462 push.../../node_modules/zone.js/dist/zone.js.Zone._updateTaskCount @ zone.js:284 push.../../node_modules/zone.js/dist/zone.js.Zone.runTask @ zone.js:205 drainMicroTaskQueue @ zone.js:601 push.../../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask @ zone.js:507 invokeTask @ zone.js:1671 globalZoneAwareCallback @ zone.js:1697 core.js:4117 ERROR Error: Uncaught (in promise): TypeError: Constructor cannot be invoked without 'new' TypeError: Constructor cannot be invoked without 'new' at Object.apply (polytype.mjs:43) at new PolyTypesComponent (poly-types.component.ts:36) at NodeInjectorFactory.PolyTypesComponent_Factory [as factory] (poly-types.component.ts:39) at getNodeInjectable (core.js:3956) at instantiateAllDirectives (core.js:8407) at createDirectivesInstances (core.js:7774) at ɵɵelementStart (core.js:14518) at Module.ɵɵelement (core.js:14569) at LoginFormComponent_Template (login-form.component.html:85) at executeTemplate (core.js:7747) at resolvePromise (zone.js:832) at resolvePromise (zone.js:784) at zone.js:894 at ZoneDelegate.push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421) at Object.onInvokeTask (core.js:28122) at ZoneDelegate.push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420) at Zone.push.../../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188) at drainMicroTaskQueue (zone.js:601) at ZoneTask.push.../../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:507) at invokeTask (zone.js:1671) defaultErrorLogger @ core.js:4117 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.ErrorHandler.handleError @ core.js:4165 next @ core.js:28692 schedulerFn @ core.js:25506 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub @ Subscriber.js:192 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next @ Subscriber.js:130 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next @ Subscriber.js:76 push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next @ Subscriber.js:53 push.../../node_modules/rxjs/_esm5/internal/Subject.js.Subject.next @ Subject.js:47 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.EventEmitter.emit @ core.js:25488 (anonymous) @ core.js:28157 push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:386 push.../../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:143 push.../../node_modules/@angular/core/__ivy_ngcc__/fesm5/core.js.NgZone.runOutsideAngular @ core.js:28074 onHandleError @ core.js:28157 push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.handleError @ zone.js:390 push.../../node_modules/zone.js/dist/zone.js.Zone.runGuarded @ zone.js:157 _loop_1 @ zone.js:701 api.microtaskDrainDone @ zone.js:708 drainMicroTaskQueue @ zone.js:608 push.../../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask @ zone.js:507 invokeTask @ zone.js:1671 globalZoneAwareCallback @ zone.js:1697

Method override does not work correctly with "crocks" library

Steps to reproduce:

const Arrow = require("crocks/Arrow");

class K extends classes(Arrow, Object) {
  compose(...args) {
    console.log("THIS WILL NOT RUN");
    return super.compose(...args);
  }
}

const k = new K([x => x]);
const l = new K([x => x]);
k.compose(l);  // returns an instance of Arrow but no console.log

Using generic in baseclass

Hi, I am working on an angular project which uses typescript and was trying to use polytype.
I have a case where I am trying to use polytype in something like:

class WriteService<T> {
    create(t: T): T;
    update(t: T): T;
}

export class UserService extends WriteService<UserModel> {
    ...
}

i.e. my BaseService is defining a bunch of functions for my Type - and then I inherit this into a UserService with my UserModel.

When I try to use polytype:

export class UserService extends classes(WriteService<UserModel>) {
    ...
}

it gives me an error - so, I tried a few variations of this (without much understanding of the magic polytype does), but was not able to figure out how to handle this case with polytype.

Some variations I tried:

  • Sending as a generic: export class UserService<T> extends classes(WriteService<T>)
  • Giving classes an explicit type: export class UserService extends classes<[WriteService<UserModel>]>(WriteService)
  • Giving classes an explicit type: export class UserService extends classes<[typeof WriteService<UserModel>]>(WriteService)

I guess the argument to classes is a class. So, I can't pass the <> there... ?

Issue with Angular module

When i use Polytype with Angular 8 i get this error:

Unexpected value 'AppComponent' declared by the module 'AppModule'

AppComponent:

import { Component, OnInit } from '@angular/core';
import { ClassA } from './classA';
import { ClassB } from './classB';
import { classes } from 'polytype';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.sass']
})
export class AppComponent extends classes(ClassA, ClassB) implements OnInit  {

  constructor() {
    super(['hola'], ['adios']);
  }

  ngOnInit(): void {
    this.printA();
    this.printB();
  }
}

AppModule

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

If I delete extends classes(ClassA, ClassB) everything works correctly

Bind methods problem

Hi Francesco!

Thank you for Polytype, it's very well done and carefully explained. 😃

I'm experiencing the following issue, when dealing with binds in base classes.
If I use only extends in this scenario it works as expected.

import { classes } from 'js/polytype'

class TestDefault {
    constructor() {
        this.print = this.print.bind(this)

        console.log('TestDefault constructor')
    }

    print() {
        console.log('TestDefault Print')
    }
}

class Test extends classes(TestDefault) {
    constructor() {
        super()

        this.someVar = 6

        console.log('Test constructor')
    }

    print() {
        console.log('Test Print', this.someVar)

        super.print()
    }
}

const test = new Test()

test.print()

This will print:

TestDefault constructor
Test constructor
Test Print undefined
TestDefault Print

Instead of Test Print undefined, it should be be Test Print 6.

I'm guessing the binding only binds the instance of TestDefault.
I've tried to bind again print in Test, but nothing changed.

Do you know what can be done to solve this?

Thank you!

Issue with Angular 8 in prod mode

When we launch the following command:
ng serve --prod --source-map

We get the following error:

ERROR in main.d558a3403cc855f52798.js from Terser
Unexpected token: punc ()) [./node_modules/polytype/lib/polytype.mjs:117,0][main.d558a3403cc855f52798.js:52044,4]

empty catch block - not accepted by some linters

theres an empty catch block in polytype.cjs file at line 526, it works fine without issues but some linters are now allowing it to be uploaded to proxy repos. can we add a simple command to handle the exception?

Comparing polytype with other methods

I've been trying to figure out inheritance / mixin approach for the past few days.
And I've seen a few methods around

polytype seems to be the most robust approach I have come acorss based on the documentation so far.
I was wondering if there was some nice comparison of some of the common approaches and pitfalls or other approaches that could exist ?

For example -

  1. The single inheritance available in JS now prototype replacement (which does not allow multiple inheritance)
  2. The approach of modifying the prototype of classes - like Object.assign(MyClass.prototype, MyMixin); (which does not allow me to use super(), instanceof, doesnt handle typing, etc.)
  3. The approach mentioned in typescript's mixin documentation right now about dynamic classes inherited in a function (Which has a bit of an awkward syntax ? But seems to handle everything else ?)
  4. ts-mixer

And so on.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.