tannerntannern / ts-mixer Goto Github PK
View Code? Open in Web Editor NEWA small TypeScript library that provides tolerable Mixin functionality.
License: MIT License
A small TypeScript library that provides tolerable Mixin functionality.
License: MIT License
import LRU from '../';
describe('clear() sets the cache to its initial state', () => {
console.dir(LRU)
});
● Test suite failed to run
TypeError: Object prototype may only be an Object or null: undefined
at Function.create (<anonymous>)
at Object.exports.hardMixProtos (node_modules/ts-mixer/dist/util.js:54:31)
at Object.Mixin (node_modules/ts-mixer/dist/mixins.js:16:18)
If you try to inherit from a native Element such as HTMLElement, then you get an Uncaught TypeError: Illegal invocation.
import { Mixin } from 'ts-mixer';
import { BaseController } from './../controllers/BaseController';
export class BaseComponent extends Mixin(HTMLElement, BaseController) {
constructor() {
super();
...
I think this can become the best solution for mixins. If you look for https://www.npmjs.com/package/ts-mixins you will see that there is the issue, that the first instance of my Controller is an instance of BaseComponent and later its an instance of a native HTMLElement. As a result, the constructor acts on the wrong object. (But if u use this package you must change HTMLElement and BaseController)
But this only by the way... Please think of it ;)
Try your awesome Mixins with Webcomponents... This is very important for at least me =)
I managed to set up your library, but for some reason I get an error that some methods are not described in the classes (they should not be described, since I expect that they will be used from the parent class)
error TS2739: Type 'NinjaOneDeviceWorkerItem' is missing the following properties from type 'IWorkerItem': init, postProcess, needProcess
Here is the class that Mixin uses (it extends NinjaOneWorkerItem and DeviceWorkerItem)
Here is NinjaOneWorkerItem which extends WorkerItem (parent)
Here is DeviceWorkerItem which also extends WorkerItem (parent)
And here is parent WorkerItem (which contain default init, postProcess, needProcess)
I have no idea what it could be related to.
Hi, @tannerntannern .
Thanks for your fantastic job! I'm happy with that repo!
however, i found there's some issue that ts-mixer doesn't work. the error message is:
MissingReflectMetadata: Could not reflect metadata of type design:type, did you forget to enable "emitDecoratorMetadata" on compiler options?
even I have opened this switch.
i rebuild a repo here which can reproduce this error : https://github.com/flame4/ts-mixer-demo.
Actually i just learned ts for 2 months so i don't know too much underlayer details, please take a look on this repo thanks!
The tsyringe package - a popular dependency injection framework - uses class (e.g. @injectable
) and constructor parameter (e.g. @inject
) decorators. Can ts-mixer
work with these decorators? I've tried and I'm getting errors.
It would be nice if there was es module build of ts-mixer. Starting with Angular v10, commonJS dependencies produce "CommonJS or AMD dependencies can cause optimization bailouts" warning when building the application and we could potentially reduce bundle size when using es module.
Can you provide "ts-mixer" packaged as ES module?
Caveat 3 states there is no way to call the constructor of a class with the correct this
context. This is in fact not true.
As can be seen in the examples at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct a simple statement change can achieve just that.
// Replace
new constructor(...args);
// With
Reflect.construct(constructor, args, this);
Am I missing something here?
I believe I see where this change would need to be made, but I'm not 100% sure so I am making this issue first.
First of all, I would like to congratz you for your useful package.
But I have a suggestion or maybe a bug fix, depending on whether it's a design or not xD
I have been using the mix decorator in some classes for about 1 month and it's a bit confusing having all of them with the same name (MixedClass) as it's showed in the picture.
Just copying the property constructor.name in the class returned by the decorator would solve this issue.
Best regards,
My use case is simple. I use TypeORM as my ORM and we're trying to do something like this.
Class Schema {
@Column(...)
public id: string;
}
class DefaultAble {
{
@Column(...)
public isDefault: boolean;
}
class SomeEntity extends Mixin(Schema, DefaultAble)
{}
The problem is the fact that I do get SomeEntity that's a real mixin of the two classes, but lose the two decorators. As a result, the two properties do not get recognized as columns in TypeORM.
I am aware that I could manually re-apply the decorators by doing some shenanigans like Column(...)(SomeEntity, "id")
, but is there possibly a more seamless option?
Webpack throws Module not found: Error: Can't resolve 'is-class' in '/project/node_modules/ts-mixer/dist'
during compilation process.
dist/mixins.js
contains var isClass = require("is-class");
on line 3, but the dependencies list is empty in package.json
.
Hello. Thanks for making a great library.
I usually use this library with Angular, but when I set Angular to v15, it compiles fine with ng serve
, but with ng test
I get the following error.
TypeError: Cannot use 'in' operator to search for 'ngOnChanges' in undefined
The Angular component class under test is as follows.
class Foo {
echoFoo(): void {
console.log('foo');
}
}
class Bar {
echoBar(): void {
console.log('bar');
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent extends Mixin(Foo, Bar) implements OnInit {
title = 'check-ts-mixer-angular15';
ngOnInit(): void {
this.echoFoo();
this.echoBar();
}
}
The error occurs when looking for the Angular lifecycle hook method from the prototype of the parent class of the Angular component, but this error does not occur when only one class is specified as the argument of Mixin, and this error seems to occur when two or more are specified.
I apologize if this is the wrong place to ask this, but is there any solution to this problem?
Here is the configuration when I tested locally.
Node.js : 14.20.0 or 18.12.1
npm : 6.14.17 or 8.19.2
Angular : 15.0.1
ts-mixer : 6.0.2
I have created a github repository with a dependency on this library added to a minimal Angular project created with ng cli, so I hope this helps.
https://github.com/gkasse/check-ts-mixer-angular15
Also, the sample repository uses karma as a test runner, but I have the same problem when testing with jest in another project.
Thank you in advance.
I'd like to use abstract classes for mixins to impose requirement on the classes that use them, like this:
import { Mixin } from 'ts-mixer'
class Parent {
}
abstract class TestMixin {
public get bar() {
return this.foo
}
protected abstract get foo(): string
}
class Test extends Mixin(Parent, TestMixin) {
protected get foo(): string {
return 'foo'
}
}
This produces an error on the Mixin
line:
Cannot assign an abstract constructor type to a non-abstract constructor type. TS2345
Could something like this be possible?
After the update to 6.0.3 my project is broken, no errors it just does not mix up
I'm working with class validator and ts-mixer, but it seems to have a problem inheriting inherited decorators. It inherits the decorators from the parent, but not from grandparents.
Example:
class Disposable {
@decorate(IsBoolean()) // instead of @IsBoolean()
isDisposed: boolean = false;
}
class Statusable {
@decorate(IsIn(['red', 'green'])) // instead of @IsIn(['red', 'green'])
status: string = 'green';
}
class Statusable2 {
@decorate(IsIn(['red', 'green'])) // instead of @IsIn(['red', 'green'])
other: string = 'green';
}
class ExtendedObject extends Mixin(Disposable, Statusable) {}
class ExtendedObject2 extends Mixin(Statusable2, ExtendedObject) {}
const extendedObject = new ExtendedObject2();
extendedObject.status = 'blue';
extendedObject.other = 'blue';
extendedObject.isDisposed = undefined;
validate(extendedObject).then(errors => {
console.log(errors);
});
This causes only validations from Statusable2
to be triggered, and the ones from ExtendedObject
to be ignored.
A workaround is to do Mixin(Statusable2, Disposable, Statusable, ExtendedObject)
, but the best solution for maintainability.
Is there a way to check if an object uses a certain mixin (its class is defined with that mixin)? My use case for example would be
this.map.entities().forEach((entity) => {
if (hasMixin(entity, Actor)) {
const actor = entity as unknown as Actor;
// use as Actor
}
});
I could work around this by checking if the object has a certain function, but I would prefer a more general solution (since this solution would be specific to the methods of each mixin)
Getting this with TypeORM decorator:
import { Entity, CreateDateColumn } from 'typeorm'
import { decorate } from 'ts-mixer'
@Entity()
export class CreatedAt {
@decorate(CreateDateColumn())
createdAt!: Date
}
(alias) CreateDateColumn(options?: ColumnOptions | undefined): Function
import CreateDateColumn
This column will store a creation date of the inserted object. Creation date is generated and inserted only once, at the first time when you create an object, the value is inserted into the table, and is never touched again.
Argument of type 'Function' is not assignable to parameter of type 'PropertyDecorator | MethodDecorator | ClassDecorator'.
Type 'Function' is not assignable to type 'ClassDecorator'.
Type 'Function' provides no match for the signature '<TFunction extends Function>(target: TFunction): void | TFunction'.ts(2345)
Any ideas?
Thank you!
Consider the following case:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { decorate, Mixin } from 'ts-mixer';
export type PostDocument = Post & Document;
@Schema()
class Likeable{
@decorate(Prop())
likes:number
}
@Schema()
class Tagable{
@decorate(Prop())
tags:string[]
}
@Schema({ timestamps: true })
class Post extends Mixin(Tagable,Likeable){
@Prop()
title:string;
@Prop()
body:string;
}
export const PostSchema = SchemaFactory.createForClass(Post);
Now using mongoose to update the document will ignore the Mixin props
// get the post document then do
post.likes = 10;
post.save();
the document will not change in the database. but if you used only one mixin the above example will work.
// This will work fine (Note only used one mixin)
@Schema({ timestamps: true })
class Post extends Mixin(Likeable){
@Prop()
title:string;
@Prop()
body:string;
}
Using tx-mixer 4.0.0 with react-scripts 3.3.0 + typescript 3.7.2, I'm getting "TypeError: Cannot call a class as a function" inside the constructor of a mixin-using class, but only in the production build. The development build works. I saw #2 but it seems that affected all builds and was resolved. Any ideas?
Trying to use Settings
gives the error Module '"ts-mixer"' has no exported member 'Settings'
. See the playground for an MWE.
First of all, thanks for your precious time and efforts 🙏🙏🙏
Extend #15 (comment)
Status quo:
import 'reflect-metadata'
import { Mixin, decorate } from 'ts-mixer'
import { IsInt, IsNotEmpty, IsUrl, IsEmail, Min, Max } from 'class-validator'
class User {
@decorate(IsInt())
id!: number
@decorate(IsNotEmpty())
name!: string
}
class Profile {
// Looks a bit verbose and repetitive 😢
@decorate(IsInt())
@decorate(Min(0))
@decorate(Max(120))
age!: number
@decorate(IsUrl())
avatar!: string
}
class UserProfile extends Mixin(User, Profile) {
@IsEmail()
email!: string
}
Proposal:
import 'reflect-metadata'
import { Mixin, decorate } from 'ts-mixer'
import { IsInt, IsNotEmpty, IsUrl, IsEmail, Min, Max } from 'class-validator'
class User {
@decorate(IsInt())
id!: number
@decorate(IsNotEmpty())
name!: string
}
class Profile {
// Looks better 😂
@decorate(
IsInt(),
Min(0),
Max(120)
)
age!: number
@decorate(IsUrl())
avatar!: string
}
class UserProfile extends Mixin(User, Profile) {
@IsEmail()
email!: string
}
The fact that mixins are being tracked globally in a map leads to a memory leak as the mixins are never unregistered from the map, preventing garbage collection of the respective classes.
ts-mixer/src/mixin-tracking.ts
Line 5 in 2e433b4
Simple solution would be to use a WeakMap.
When I try to run a test on a class I've applied a mixin to I get this error:
TypeError: Cannot redefine property: Symbol(Symbol.hasInstance)
at Function.defineProperties ()
Any thoughts on how I can get around this?
class Class0 {
public name;
}
abstract class ClassA {
protected object0: Class0;
protected name;
protected test;
init(test: string, object0?: Class0): void {
this.name = this.constructor.name;
this.test = test;
this.object0 = object0 ? object0 : new Class0();
}
getName(): string {
return this.name;
}
getTest(): string {
return this.name;
}
}
class ClassB extends ClassA {
protected name1;
init(test: string, object0?: Class0): void {
super.init(test, object0);
this.name1 = this.name + 1;
}
getName1(): string {
return this.name1;
}
}
const b = new ClassB('BB'); // Not possible
compared to the mixin function provided by TypeScript.
CODE
import { IsBoolean, IsIn, validate } from "class-validator";
import { Mixin } from 'ts-mixer'
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name));
});
});
}
// Disposable Mixin
class Disposable {
@IsBoolean()
isDisposed: boolean = false;
}
// Activatable Mixin
class Statusable {
@IsIn(['bound', 'open'])
status: string = 'test';
}
class ExtendedObject extends Mixin(Disposable, Statusable) {
}
class MixedObject {
constructor() {
this.status = 'test'
this.isDisposed = false
}
}
interface MixedObject extends Disposable, Statusable {}
applyMixins(MixedObject, [Disposable, Statusable]);
async function test(classObj)
{
let smartObj = new classObj();
console.log(smartObj)
console.log(smartObj.status)
console.log(smartObj.isDisposed)
console.log(await validate(smartObj))
}
async function main() {
await test(MixedObject)
console.log('-----------')
await test(ExtendedObject)
}
main()
OUTPUT
Statusable { status: 'test', isDisposed: false }
[
ValidationError {
target: Statusable { status: 'test', isDisposed: false },
value: 'test',
property: 'status',
children: [],
constraints: { isIn: 'status must be one of the following values: bound,open' }
}
]
-----------
ExtendedObject { isDisposed: false, status: 'test' }
[]
Hi, I've just discovered your library (and it seems to be awesome) but I'm encountering a weird error:
TypeError: (0 , _1.withC) is not a function
While executing the following:
import { Mixin } from 'ts-mixer';
const withC = (T: B | D) => class extends (T instanceof B ? B : D) {
// some code here that should inherit B or D classes
}
class A extends Mixin(B, withC(new B())) {
// ^
// TypeError: (0 , _1.withC) is not a function
}
Is there anything related to your library? Or I presume I'm doing something wrong, but I'm not experienced enough to see what I do wrong aha :/
Ts-mixer doesnt work in IE11. IE11 doesnt support ES6 syntax. Any workaround or fix?
export class LRU<KeyType, ValueType> extends Mixin(EventEmitter, QuickLRU<KeyType, ValueType>)
Hi,
first of all thank you for this amazing library.
I was wondering if would be possible to extend an abstract class (along with some others) that needs a T.
For example:
export abstract class MyAbstractClass<T> { ... }
and then in the main component :
export class MyComponent extends Mixin(MyAbstractClass<string>, foo, bar, ...) { ... }
Maybe I'm missing something but I can't figure how to do it.
The error given is:
Value of type 'typeof MyAbstractClass' is not callable. Did you mean to include 'new'?
Have you seen https://devblogs.microsoft.com/typescript/announcing-typescript-4-2-beta/#abstract-construct-signatures? Will caveat 1 be solvable after that? https://github.com/tannerntannern/ts-mixer#caveats
I'm trying to use this with classes which have metadata added via reflect-metadata
.
Previously I was using the old TS mixin style (https://www.typescriptlang.org/docs/handbook/mixins.html) but all of my classes are generic and the hoops that I need to jump through are extreme!
I think the typings you have done a re really nice and it's a decent way of achieving a similar result.
I have a lot of code which relies on reflect-metadata and also on generic mixins. In hindsight I wish I hadn't written it but it's too late now! It also has quite a few places where it's relying on instanceof as well.
I have forked your code and made some modifications for my use case, I added an Augment(Base, Extras)
function which will subclass the Base
class and mixin the Extras
class. This way reflect-metadata and instanceof both work for the Base class (but not the Extras of course).
I also added a @base
decorator which works similarly to @mix
but it subclasses the first argument passed to it.
I will send you a PR but I will not be offended if you don't want to add support for these use cases.
Line 148 in 096e889
const props = Object.getOwnPropertyDescriptors(protoChain[i]);
delete props.constructor;
Object.defineProperties(mixedClassProto, props);
Hello! I just installed your module to our project and unfortunely we met an issue during implementation.
We have a lot of nested classes what have statics attributes\methods. But these methods are not accessible when the classes are loaded through your Mixin.
I have created a simple example for you.
import {Mixin} from "ts-mixer";
class TEST1 {
static a() {
console.log("A")
}
}
class TEST2 extends Mixin(TEST1) {}
window.TEST2 = TEST2;
If I type TEST2.a
to console. The function is undefined.
Maybe it could be related to @babel/plugin-proposal-class-properties with @babel/plugin-transform-typescript. We are not using the babel's syntax plugin but only transform.
Let me know if you need any help. But to me, seems this should be part of ts-mixer and it should work.
Thank you very much.
package.json
@babel/plugin-transform-typescript: 7.5.5,
@babel/plugin-proposal-class-properties: 7.5.5,
ts-mixer: 3.1.2,
typescript: 3.5.3,
babel.config.js
const presets = [
[
"@babel/env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "70",
safari: "11.1",
},
useBuiltIns: "usage",
corejs: {
version: 3,
proposals: true,
},
},
],
// ["@babel/preset-typescript"], disabled because of bug with auto-assign in constructor. Instead of this is used ["@babel/plugin-transform-typescript"]
]
const plugins = [
["@babel/plugin-transform-typescript"],
["@babel/plugin-syntax-dynamic-import"],
["@babel/plugin-proposal-decorators", {decoratorsBeforeExport: false}],
["@babel/plugin-proposal-class-properties"],
["@babel/plugin-proposal-private-methods"],
["@babel/plugin-proposal-optional-chaining"],
["@babel/plugin-proposal-nullish-coalescing-operator"],
["@babel/plugin-proposal-throw-expressions"],
[
"@babel/plugin-transform-runtime",
{
corejs: {
version: 3,
proposals: true,
},
helpers: true,
regenerator: true,
useESModules: false,
},
],
]
module.exports = {presets, plugins}
I have code like
import { Mixin } from 'ts-mixer';
class Foo {
public a: string;
protected makeFoo() {
return 'foo';
}
public constructor(a:string)
{
this.a = a
}
}
class Bar {
public b: string;
public c: string;
public constructor(b:string,c:string)
{
this.b = b
this.c = c;
}
protected makeBar() {
return 'bar';
}
}
class FooBar extends Mixin(Foo, Bar) {
public constructor()
{
super("a")
}
public makeFooBar() {
return this.makeFoo() + this.makeBar();
}
}
const fooBar = new FooBar();
how can I set value to property "a" of super class Foo and properties b and c of super class Bar in constructor of FooBar?
if I use super("value") then, both a and b will have value "value"
what if I want to assign different value to a, b and c in constructor of FooBar?
When mixin with Litelement/webcomponent classes you will get a Illegal Constructor
error.
At the moment ts-mixer
supports class-validator quite well except the scenario requires class-transformer.
For example: https://stackoverflow.com/a/58366367/5172890 , @Type
is required for nested collection objects validation.
Hi there,
Let me try to describe the issue with ts-mixer I'm facing right now. So I'm using nestjs as the main dev framework for the backend and currently implementing the register method that would allow the application to register all types of users. There're a few possible types of users here. Some of them are FAN, FIGHTER, JUDGE etc. Each user has its own set of specific parameters. For example, FIGHTER has such params as height
, weight
, wins
etc that are specific to fighters only.
So far the register method looks like this:
@Post('register')
public async registerUser(@Body() registerUserDto: RegisterUserDto): Promise<void> {
// code
}
That RegisterUserDto
is supposed to contain all the fields coming from all types of users, but frontend-wise, when I register a specific user I should be allowed to specify only those json fields relevant to the type of user I'm registering. It means that the majority of fields defined on RegisterUserDto
will be optional. The thing is that, in order to improve code readability and code maintenance, I would like to have separate dtos for each possible type of user and then pile them all together into a single object (dto) using multiple inheritance or the intersection type. The second option does not work cos in that case the class-validator
and nestjs/swagger libs stop seeing their decorators. The first option seems to be sort of doable thanks to the ts-mixer library, cos as it's stated in its documentation we should wrap validation decorators in the @decortor
decorator. It works like a charm, but...it's not the case with the nestjs/swagger decorators and that's the problem I'm trying to solve. For instance, when I use @ApiProperty
decorator on any of the defined props I get this error:
(node:22654) UnhandledPromiseRejectionWarning: Error: A circular dependency has been detected (property key: "firstName"). Please, make sure that each side of a bidirectional relationships are using lazy resolvers ("type: () => ClassType").
at SchemaObjectFactory.createNotBuiltInTypeReference (/Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:212:19)
at SchemaObjectFactory.mergePropertyWithMetadata (/Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:143:25)
at /Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:79:35
at Array.map (<anonymous>)
at SchemaObjectFactory.extractPropertiesFromType (/Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:78:52)
at SchemaObjectFactory.exploreModelSchema (/Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:92:41)
at /Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:33:36
at Array.map (<anonymous>)
at SchemaObjectFactory.createFromModel (/Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:20:45)
at exploreApiParametersMetadata (/Users/albert/Documents/projects/albert/rlx/node_modules/@nestjs/swagger/dist/explorers/api-parameters.explorer.js:33:55)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:22654) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:22654) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Here is more code:
export class RegisterCommonUserDto {
@decorate(ApiProperty())
@decorate(IsNotEmpty())
@decorate(IsString())
firstName: string;
// @decorate(ApiProperty())
@decorate(IsNotEmpty())
@decorate(IsString())
lastName: string;
// @decorate(ApiProperty({enum: EUserRoleName}))
@decorate(IsNotEmpty())
@decorate(IsEnum(EUserRoleName))
roleName: EUserRoleName;
// @decorate(ApiProperty())
@decorate(IsNotEmpty())
@decorate(IsPhoneNumber())
phoneNumber: string;
// @decorate(ApiProperty())
@decorate(IsNotEmpty())
@decorate(IsEmail())
email: string;
}
and the RegisterUserDto
looks as follows:
export class RegisterUserDto extends Mixin(
RegisterFighterDto,
RegisterCommonUserDto,
RegisterLocationProviderDto,
) {}
I would appreciate your help!
Here is the code:
class MixinA {
foo = 'a';
method() {
console.log('method from A');
}
}
class MixinB {
foo = 'b';
method() {
console.log('method from B');
}
}
@mix(MixinA, MixinB)
class MixedWithDecorator {
foo = 'mixed';
method() {
console.log('method from Mixed');
}
}
class MixedWithExtends extends Mixin(MixinA, MixinB) {
foo = 'mixed';
method() {
console.log('method from Mixed');
}
}
console.log('Decorated:');
const decorated = new MixedWithDecorator();
console.log(decorated.foo);
decorated.method();
console.log('\nExtended:');
const extended = new MixedWithExtends();
console.log(extended.foo);
extended.method();
The output is:
Decorated:
b
method from B
Extended:
mixed
method from Mixed
That's because Mixin(BaseClass, ...ingredients)
is not the same as class BaseClass extends Mixin(...ingredients)
.
Our project use babel-typescript-plugin as compiler, in generated code, here is:
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
I see that ts-mixer call constructor explicitly.
How amazing...🎩🎩🎩🎩🎩
I've tried figured out how this thing works, soon I realized, this is too hard to understand for me.
Could you add some explanation for this on the README ?? Thanks!
Hello,
Thank you for this nice library, it seems to be very interesting to use for our use cases.
However, I'm not able to use it properly in Angular. Here is an example :
// Global config for ts-mixer :
import { settings } from 'ts-mixer';
settings.prototypeStrategy = 'proxy';
settings.staticsStrategy = 'proxy';
export default settings;
// Component receiving all extending classes
@Component({
selector: 'one-forms-input-text',
templateUrl: './text.component.html',
styles: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: TextComponent,
multi: true,
},
],
})
export class TextComponent extends Mixin(
LengthValidatorTemplateMixin,
ControlValueAccessorConnector
) {
/**
* Used to implement a CSS class directly on the host
* @internal
* @protected
*/
@decorate(HostBinding('class.one-forms-input-text'))
protected override hostClass = true;
/**
* Suffix icon to display
*/
@decorate(Input())
icon: MaterialIcon = '';
constructor(injector: Injector) {
super(injector);
}
}
export class ControlValueAccessorConnector implements ControlValueAccessor {
/** Default class applied on Host element */
@decorate(HostBinding('class.one-forms-input'))
protected hostClass = true;
/** Update user-select CSS property */
@decorate(HostBinding('style.user-select'))
protected userSelectMode = 'auto';
/**
* Used to implement the display CSS property directly on the host
*
* @protected
*/
@decorate(HostBinding('style.display')) protected hostDisplay =
'inline-block';
/**
* @internal
*
* used internally to manage the form control
*/
@decorate(ViewChild(FormControlDirective, { static: true }))
formControlDirective: FormControlDirective | undefined;
/**
* First way to set the formControl by using the formControl itself
*/
@decorate(Input())
formControl: FormControl | undefined;
/**
* Another way to set the formControl by using the formControlName
*/
@decorate(Input())
formControlName = '';
/**
* Placeholder of the input, when there is no user input
*/
@decorate(Input())
placeholder = '';
/**
* Label to name the input
*/
@decorate(Input())
label = '';
/**
* Add without-label class to the host component if no label specified
* @protected
*/
@decorate(HostBinding('class.without-label'))
protected get hostWithoutLabel() {
return this.label === '';
}
/**
* The message to describe the input by setting the mat-hint
*/
@decorate(Input())
description = '';
constructor(private injector: Injector) {}
export class LengthValidatorTemplateMixin {
/**
* Minimum length for the input
*/
@decorate(Input()) minLength: string | number | null = null;
/**
* Maximum length for the input
*/
@decorate(Input()) maxLength: string | number | null = null;
}
The error thrown is:
Error: Cannot set new properties on Proxies created by ts-mixer
After some investigation, it seems there is a problem with the constructor:
Do you have any idea how I could use ts-mixer in Angular properly?
Thank you and have a nice day
I'm trying to run an example from your repository, but I'm getting an error.
I also tried to copy my ts-config into the repl.it sandbox and there is no such problem. Do you have any idea why i am getting this error
On my computer, the version of ts is 4.4.4 (the same version is in the sandbox)
Please help me figure out what could be the problem?
If you need more details please let me know.
My tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"skipLibCheck": true,
"module": "commonjs",
"esModuleInterop": true,
"noImplicitReturns": false,
"noUnusedLocals": true,
"outDir": "lib",
"sourceMap": true,
"strict": true,
"target": "es2017",
"noImplicitAny": true,
"lib": ["es2019", "es2020", "dom"],
"types": ["chai", "mocha", "node"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true
},
"compileOnSave": true,
"include": ["src"],
"exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts"]
}
Hi, @tannerntannern!
Thank you for this awesome library, it really helped me and my colleagues 🚀
But I have a small issue. I'm a bit confused on decorating methods in the class extending a mixin (or multiple mixins).
Suppose I have a base class (A
) with two methods: methodA
and methodB
which use A
's properties internally.
I have another class B
that extends A
and has a decorated method. What I want to do is to call A.methodA
and A.methodB
inside the decorator. But I'm getting the following error error "Cannot read properties of undefined" inside the A.methodA
call for some reason.
Is there a way to get the proper this
(B
instance with every property from A
) inside the decorator?
Minimal example to reproduce the issue:
import { BehaviorSubject, Observable, of } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { Mixin } from 'ts-mixer';
// I want to decorate methods that return an Observable
type ObservableMethod = (...args: unknown[]) => Observable<unknown>;
type ObservableMethodDecorator = (
target: any,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<ObservableMethod>
) => TypedPropertyDescriptor<ObservableMethod> | void;
function LockUiUntilFinish(): ObservableMethodDecorator {
return function (
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<ObservableMethod>
): TypedPropertyDescriptor<ObservableMethod> {
const originalFn = descriptor.value;
descriptor.value = (...args: unknown[]): Observable<unknown> => {
target?.lockUi?.(); // <------------ at this point target does not have the `isUiLocked$` property
return originalFn(...args).pipe(finalize(() => target?.unlockUi?.()));
};
return descriptor;
};
}
class Base {
protected isUiLocked = false;
public readonly isLoading$ = new BehaviorSubject<boolean>(true);
public readonly isUiLocked$ = new BehaviorSubject<boolean>(this.isUiLocked);
public lockUi(): void {
console.log('lockUi');
this.isUiLocked = true;
this.isUiLocked$.next(this.isUiLocked);
}
public unlockUi(): void {
console.log('unlockUi');
this.isUiLocked = false;
this.isUiLocked$.next(this.isUiLocked);
}
}
class Foo extends Mixin(Base) {
constructor() {
super();
}
@LockUiUntilFinish()
public test(): Observable<unknown> {
return of(100);
}
}
const foo = new Foo();
// I would like to get the following output:
// > lockUi
// > 100
// > unlockUi
foo.test().subscribe(console.log);
I am trying to test for the presence of an abstract mixin class and am getting a ts2345 error:
Argument of type 'typeof HasMixinTest' is not assignable to parameter of type 'new (...args: any[]) => HasMixinTest'.
Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2345)
Example code to reproduce this issue:
import { hasMixin, Mixin } from 'ts-mixer'
abstract class HasMixinTest {}
abstract class Foo extends Mixin(HasMixinTest) {}
hasMixin(Foo, HasMixinTest)
Hello again!
I have a class what inherits another class. Where the another class has static attributes but line 163 doesn't take in account inner prototypes.
Case:
class TEST0 {
static scope = 10;
}
class TEST1 extends TEST0 {}
class TEST2 extends Mixin(TEST1) {}
window.TEST2 = TEST2
scope
is undefined at TEST2
.
Here is an example of constructor to describe the issue:
Class2 constructor contains:
{
"tagName",
"prototype": Class1
}
Class1 Constructor:
{
"scope": 10
}
But prototype
is restricted from getOwnPropertyNames
and scope
will be skipped.
Firstly, this lib is neat - thanks for making it!
One thing I'm struggling with (I don't know if it's even possible or how it would work) is how to mix two classes that require constructor args. Here's an example:
import { mix } from 'ts-mixer';
import { Class1, Class2 } from './example';
export interface IMixed<T1, T2> extends Class1<T1>, Class2<T2> {
}
@mix(Class1, Class2)
class Mixed<T1, T2> implements IMixed<T1, T2> {
constructor(args) {
super(args); // How would you pass args to the correct class instance here?
}
}
Is there a different pattern I could follow to get a similar result? Any help would be appreciated!
Hi,
I've upgraded to v5 and in my project (typescript -> babel -> js) a getter and setter method was not called any more in a class which was using a mixin.
Right now i've downgraded, next week I have more time to dive into the manner...
thanks 👍
When using regular heritance:
class Parent{
constructor() {
console.log('Parent name:'+ this.constructor.name)
}
}
class Child extends Parent{
constructor() {
super()
console.log('Child name:'+ this.constructor.name)
}
}
let child = new Child();
Outputs:
Parent name:Child
Child name:Child
But when I use Mixin from ts-mixer:
import { Mixin } from 'ts-mixer';
class Parent {
constructor() {
console.log('Parent name:' + this.constructor.name);
}
}
class Child extends Mixin(Parent) {
constructor() {
super();
console.log('Child name:' + this.constructor.name);
}
}
const child = new Child();
Outputs:
Parent name:Parent
Child name:Child
So it's not getting the correct "this"
Hey, I'm using your library for a project that needs 12/13 mixin classes (I know weird) but apparently this library only handles 10? I read bit of the code and saw that the function is declared manually. Is there a possibility to add as much as I need in terms of classes or is that a limit ?
If I instantiate a mixin in another ts file I get a error.
Here is the code in a file (it works).
export abstract class BaseApi {
protected readonly apiUrlPart: string;
protected constructor( apiUrlPart: string ) {
this.apiUrlPart = apiUrlPart;
}
}
export class ApiCreate<T extends Entity> extends BaseApi {
public constructor( apiUrlPart: string ) {
super( apiUrlPart );
}
public create( entity: T ): Observable<T> {
return null;
}
}
export class ApiQuery<T extends Entity> extends BaseApi {
public constructor( apiUrlPart: string ) {
super( apiUrlPart );
}
public query( id: string | number ): Observable<T> {
return null;
}
}
export class ApiQueryAll<T extends Entity> extends BaseApi {
public constructor( apiUrlPart: string ) {
super( apiUrlPart );
}
public queryAll( entity: T ): Observable<T> {
return null;
}
}
export class ApiUpdate<T extends Entity> extends BaseApi {
public constructor( apiUrlPart: string ) {
super( apiUrlPart );
}
public update( entity: T ): Observable<T> {
return null;
}
}
export class ApiDelete<T extends Entity> extends BaseApi {
public constructor( apiUrlPart: string ) {
super( apiUrlPart );
}
public delete( entity: T ): Observable<T> {
return null;
}
}
@MixinDecorator( ApiCreate, ApiQuery, ApiQueryAll, ApiUpdate, ApiDelete )
export class CrudApi<T extends Entity> extends BaseApi {
public constructor( apiUrlPart: string ) {
super( apiUrlPart );
}
}
export interface CrudApi<T extends Entity> extends ApiCreate<T>, ApiQuery<T>, ApiQueryAll<T>, ApiUpdate<T>, ApiDelete<T> {}
const a: CrudApi<Rocket> = new CrudApi<Rocket>( 'rocket' ); // <-- here it works
But that does not work:
File api.ts
// tslint:disable variable-name max-classes-per-file
import { CrudApi } from './mixins.ts';
import { Rocket} from './rocket';
export class RocketApi {
public readonly Rocket: CrudApi<Rocket> = new CrudApi<Rocket>( 'rocket' );
}
Error:
main.ts:12 TypeError: WEBPACK_IMPORTED_MODULE_0__.CrudApi is not a constructor
at ...
at _createClass (core.js:21165)
at _createProviderInstance (core.js:21137)
at initNgModule (core.js:21070)
at new NgModuleRef_ (core.js:21797)
at createNgModuleRef (core.js:21786)
at Object.debugCreateNgModuleRef [as createNgModuleRef] (core.js:23617)
at NgModuleFactory_.push../node_modules/@angular/core/fesm5/core.js.NgModuleFactory_.create (core.js:24321)
at core.js:17755
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.