-
Notifications
You must be signed in to change notification settings - Fork 431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't use @Component on TypeScript abstract component class #91
Comments
You also need to annotate your parent class with vue-class-component will resolve your component inheritance only if all components are annotated. |
I tried to do this, but the abstract class has a generic type, and it seems @component doesn't support this (I use TypeScript). |
Well in fact, problem is not caused by the generic type, but it's caused by the fact the class is abstract. |
It works when removing abstract keyword from class, and implementing previous abstract methods with errors. But it's a lose of some TypeScript compile-time checking here :( @Component
class DataEditorVue<T extends AbstractData> extends Vue {
creation: boolean = false;
data: T = null;
created() {
if (!this.data) {
this.creation = true;
this.data = this.buildDataInstance();
}
}
buildDataInstance(): T {
throw new Error('Not implemented !');
};
getApiResource(): AbstractDataResource<T> {
throw new Error('Not implemented !');
};
getRouterPath(data: T): string {
throw new Error('Not implemented !');
};
getRouterPathAfterDeletion(data: T): string {
throw new Error('Not implemented !');
};
remove() {
return this.getApiResource().deleteFromObject(this.data).then(() => {
return this.$router.push(this.getRouterPathAfterDeletion(this.data));
});
}
create() {
return this.getApiResource().create(this.data).then((data) => {
return this.$router.push(this.getRouterPath(data), () => {
this.creation = false;
this.data = data;
});
});
}
update() {
return this.getApiResource().update(this.data);
}
} Do you think it could be possible to make it work with my first proposal for TypeScript users ? |
https://github.com/vuejs/vue-class-component/blob/master/src/declarations.ts#L3
I would recommend you to use mixin with interface instead of abstract class. |
I see ! Thanks for help, i'll have a look to mixins. |
Hi,@HerringtonDarkholme thanks for your sharing . @Component
class HelloTrait extends Vue {
sayHi(name: string): void {
alert(`Hi ${name}! this from HelloTrait`)
}
} and mixin it , @Component({
components: {"test-slot": testSlot},
mixins: [HelloTrait]
})
class SorterList2VC extends BaseVueList<Sorter> {
test2() {
this.sayHi("json")
} |
@178220709 You simply need a util function Example: |
@HerringtonDarkholme |
I'm confused. To use the mixing function, we need to switch to av-ts? |
@304NotModified you also can use mixin in this project . /**
* Created by jsons on 2017/5/27.
*/
import {Vue} from 'vue/types/vue'
import {ComponentOptions, FunctionalComponentOptions} from 'vue/types/options'
import {componentFactory} from "vue-class-component/lib/component";
function ComponentForMixin<V, U extends Vue>(options: ComponentOptions<U> | V): any {
if (typeof options === 'function') {
return componentFactory(options as any)
}
return function (Component: V) {
return componentFactory(Component as any, options)
}
}
type VClass<T extends Vue> = {
new(): T
extend(option: ComponentOptions<Vue> | FunctionalComponentOptions): typeof Vue
}
function Mixin<T extends Vue>(parent: typeof Vue, ...traits: (typeof Vue)[]): VClass<T> {
return parent.extend({mixins: traits}) as any
}
export {ComponentForMixin, Mixin} and use by this import {Component, ComponentForMixin, Vue, Mixin} from "typings/base"
declare interface ApplePenTrait extends Pen, Apple, TestClass3 {
}
@Component
class Pen extends Vue {
havePen() {
alert('I have a pen')
}
}
@Component
class Apple extends Vue {
haveApple() {
alert('I have an apple')
}
}
@Component
class TestClass3 extends Vue {
str3 = "TestClass3"
}
// compiles under TS2.2
@ComponentForMixin({
template: `<span @click="Uh"> click show</span>`
})
export default class ApplePen extends Mixin<ApplePenTrait>(Apple, Pen, TestClass3) {
havePen() {
alert('I have a pen (ApplePen)')
}
Uh() {
this.havePen()
this.haveApple()
alert(this.str3)
}
} the idea is copy from @HerringtonDarkholme 's av-ts . |
Thanks! Update got it working, Unfortunate I can't get it working. I have this now: the file ComponentForMixin.ts, which the class In file myfile.ts:
NB: I can't use
as ComponentForMixin.ts isn't exporting those? also, I didn't needed |
To be clear, an |
Typed mixins are too useful for me to have to manually set up for each project, so I bit the bullet and extracted the code here into a module. |
If I understand this thread correctly, it is not possible to use an abstract class on a mixin. My use-case is that my mixin expects the component it's used on implements a method. I am not sure how can I make this typesafe. What I'd love to do is this: @Component
export abstract class DiscardableFormMixin extends Vue {
public abstract hasUnsavedChanges(): boolean
public close() {
if (this.hasUnsavedChanges) {
// open discard dialog and await for user's answer
} else {
// immediately close
}
}
} So, when I use this mixin on a component, that component needs to tell the mixin what it means that there are some unsaved changes. Currently I'm unable to do this. What I tried doing was declaring an interface which the same name, which means that TS will merge the class and interface declaration into one: export interface DiscardableFormMixin {
hasUnsavedChanges: boolean
}
@Component
export class DiscardableFormMixin extends Vue {
public close() {
if (this.hasUnsavedChanges) {
// open discard dialog and await for user's answer
} else {
// immediately close
}
}
} This allows me to use @Component
export default class SomeForm extends mixins(DiscardableFormMixin) {
} I need a behavior where this produces an error, telling me that I have to provide an implementation for the What I realize I can do is declare the interface with a different name. export interface DiscardableFormMixinInterface {
hasUnsavedChanges: boolean
} Then, when using the mixin: @Component
export default class SomeForm extends mixins(DiscardableFormMixin) implements DiscardableFormMixinInterface {
} The problem with this approach is (apart from having to re-define the type of |
The last comment on this vue forum thread seems to have the current best answer; and it uses mixins - |
Outside of the interface, what else would require changing to be able to use abstract classes as mixins? because even if the interface check is overridden with an Just want to know if this is something that would be feasible in the future (or maybe even doable locally). |
That's interesting. I did actually get abstract class mixins working fine and with type safety. Just had to hack around a bit with TypeScript. Not sure how stable this implementation is, but so far it seems to work fine. Borrowing @lazarljubenovic example: // note two things:
// 1) this is not the actually exported class, even though it contains all logic (If we'd export and use this as the actual mixin, nothing would work, since I guess abstract classes are only compile time and not runtime in TS?)
// 2) the @ts-ignore comment, which makes the @Component decorator stop complaining about the fact that it does not like abstract classes
// @ts-ignore
@Component
abstract class DiscardableFormMixinAbstract extends Vue {
public abstract hasUnsavedChanges(): boolean
public close() {
if (this.hasUnsavedChanges) {
// open discard dialog and await for user's answer
} else {
// immediately close
}
}
}
// note two things again:
// 1) this is the actual export, which is an actual class than can be converted to JS
// 2) it again has @ts-ignore to make TypeScript stop complaining about the fact that it does not implement the abstract members of the base class
@Component()
// @ts-ignore
export class MessageModuleAuthorityStoreMixin extends MessageModuleAuthorityStoreMixinAbstract { } then this will have compile error on the SomeForm class name until you implement the required abstract method: @Component
export default class SomeForm extends mixins(DiscardableFormMixin) {
mounted() {
// this call is fine and works without problems
this.close();
}
} |
Amazing! Thanks a lot @ReinisV ❤️Not sure how "stable" it is either, but it is indeed working as expected, and sounds like a well contained hack! Seems like you simply forgot a import { Component, Vue, PropSync, Emit } from 'vue-property-decorator';
import { Credentials, SourceGateway } from 'my-package';
// note two things:
// 1) this is not the actually exported class, even though it contains all logic (If we'd export and use this as the actual mixin, nothing would work, since I guess abstract classes are only compile time and not runtime in TS?)
// 2) the @ts-ignore comment, which makes the @Component decorator stop complaining about the fact that it does not like abstract classes
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
@Component
abstract class Login extends Vue {
public abstract setSourceGateway(): SourceGateway;
@PropSync('credentials')
syncedCredentials!: Credentials;
@Emit()
input() {
return this.syncedCredentials;
}
mounted() {
this.$nextTick(async () => {
// we can rely on this function, which will have to be implemented
this.setSourceGateway();
});
}
}
// note two things again:
// 1) this is the actual export, which is an actual class than can be converted to JS
// 2) it again has @ts-ignore to make TypeScript stop complaining about the fact that it does not implement the abstract members of the base class
/* eslint-disable @typescript-eslint/ban-ts-ignore */
// @ts-ignore
@Component
// @ts-ignore
export default class LoginMixin extends Login {}
/* eslint-enable @typescript-eslint/ban-ts-ignore */ And the implementation in an actual component, showcasing option merging to override the Prop default value: import { Component, Prop, Emit, Mixins } from 'vue-property-decorator';
import { Credentials, MyGateway } from 'my-package';
import LoginMixin from '../abstracts/Login'; // or wherever you put it!
@Component({
components: {
Icon,
GenericInput,
},
})
export default class Login extends Mixins(LoginMixin) {
// overriding the LoginMixin defaults for credential Prop
@Prop({ default: 'a default value' }) credentials!: Credentials;
// implementing abstract setSourceGateway: without it, VSCode
// and TS will complain, which is exactly what we want here!
@Emit()
setSourceGateway() {
return new MyGateway();
}
// from there, this logic is totally specific to your component
} |
@clorichel 's approach works but I'm afraid: will it break in the future? Can I use it in long term code? @ktsn |
@rulrok Do you have any specific reason to believe it might break? |
@lazarljubenovic Just making sure because I'm still a little out of sync with the current transition of framworks to Vue 3. BTW, clorichel's even states it's kinda a hack. So, since it 'breaks' by typescript typing and we just overcome it by a |
I wrote an abstract common class that extends Vue where I want to put some common logic, including some hook methods like
created()
, but those methods are not invoked by the framework.Here's the abstract class implementing
created()
.Here the concrete class, extending abstract one.
My actual workaround is to add a
created()
method in concrete class to invokesuper.created()
manually.The text was updated successfully, but these errors were encountered: