28

I have a folder structure like this

--Page    
   -group.vue    
--Services
  -groupMixin.ts

script of group.vue

<script lang="ts">
     import { Vue, Component, Mixins } from 'vue-property-decorator'

     import { GroupMixin } from '../../services/groupMixin';
     @Component
     export default class Group extends Mixins(GroupMixin) {
        created () {
          console.log(this.test)
        }
      }
</script>

code of groupMixin.ts

import { Vue } from 'vue-property-decorator'
//creating mixins.
export class GroupMixin extends Vue {
  test: string = 'sss'
}

I am facing two problems here.

First, to import a ts file i used ../../, is there any way to use ./ or @/. Without using lang="ts", i can import a js file like this @/services/...

Second, not able to access the varaible test which i declared in groupmixin.ts.

3
  • Are you using vue-cli? Commented Aug 16, 2018 at 9:29
  • Yes I am using it. Commented Aug 16, 2018 at 19:04
  • You forgot to add @Component to groupMixin.ts Commented Oct 1, 2019 at 11:51

9 Answers 9

24

I spent a lot of time today trying to figure out how to get Vue mixins to work in a TypeScript project. Apparently all the normal ways that the tutorials say to use mixins simply do not work in TypeScript. The components don't have access to the properties defined in their mixins because apparently the Vue framework's mixin code just isn't TypeScript-friendly.

Eventually, I did find a way to get mixins working in TypeScript. Working really well, in fact. I have multiple levels of mixin inheritance in my project, with mixins extending other mixins, and it all works exactly how I expected it to. The secret was that I had to install this third-party package that someone wrote to fix mixins in TypeScript:

https://www.npmjs.com/package/vue-typed-mixins

Couple words of warning (but neither is a big deal):

  1. This plugin only works for me if I define my mixins in .ts files instead of .vue files. This wasn't a problem for me because my mixins only contain code, no html or css (and I can't think of a situation where that would even make sense).

  2. When you include a mixin on a component, make sure you do it the same way as the example on the package's website (url above). If you simply install the package without refactoring your code to follow the example on the site, it won't work.

Here's a simple example:

// /src/mixins/MyMixin.ts

import Vue from "vue";

export default Vue.extend({
    data: function () {
        return {
            mixinMessage: "this came from MyMixin!"
        };
    },
    created: function () {
        console.log("MyMixin.created()");
    },
    mounted: function () {
        console.log("MyMixin.mounted()");
    },
    methods: {
        mixinOutput: function (text:string) {
            console.log("mixin says: " + text);
        }
    }
});

Which is then used by:

// /src/components/MyComponent.vue

<template>
    <div>
        whatever
    </div>
</template>

<style>
    /* whatever */
</style>

<script lang="ts">
    import mixins from "vue-typed-mixins";
    import MyMixin from "../mixins/MyMixin";

    export default mixins(MyMixin).extend({
        created: function () {
            console.log("MyComponent.created()");
        },
        mounted: function () {
            console.log("MyComponent.mounted()");

            this.mixinOutput("hello from MyComponent");
            this.mixinOutput(this.mixinMessage);
        }
    });
</script>
Sign up to request clarification or add additional context in comments.

2 Comments

I did not use this solution, but your suggestion in (1) to use .ts instead of .vue files fixed the problem for me. No plugins required. One use-case for .Vue instead would be for example if you had some code which relied on some specific CSS, then you need to mix-in both the css and the code associated with it at the same time, from the same source... a .vue file.
This solution makes it really easy to include mulitple mixins export default mixins(mixin1, mixin2, mixin3).extend({})
12

Based on @Joe Irby's answer, I just found it works without vue-typed-mixins.

As your mixin extends Vue, you can create your component by extending your mixin:

// MyMixin.ts

import Vue from "vue";

export default Vue.extend({
    data: function () {
        return {
            message: "Message from MyMixin!"
        };
    }
});


// MyComponent.vue

<template>
    ...
</template>

<script lang="ts">
    import MyMixin from "@/mixins/MyMixin";

    export default MyMixin.extend({
        mounted: function () {
            console.log(this.message);
        }
    });
</script>

3 Comments

What if i need to use multiple different mixins?
You may try something like export default MyMixin1.extend(MyMixin2.extend({...})). Yes, it's ugly, but this solution is more a hack than a viable pattern... 😇
Excellent solution.
11

Please try to do the following to make your mixin to work:

group.vue

<script lang="ts">
 import Vue from 'vue';
 // mixins only exist in `vue-class-component` and Component is a default export.
 import Component, { mixins } from 'vue-class-component';

 import { GroupMixin } from '../Services/groupMixin';

 @Component
 export default class Group extends mixins(GroupMixin) {
    created () {
      console.log(this.test)
    }
  }
</script>

groupMixin.ts

import { Vue } from 'vue'

export class GroupMixin extends Vue {
  test: string = 'sss'
}

There is a reason that I am using importing Vue using import Vue from 'vue';, it is mainly because some IDE's are highlighting Vue functions like $emit when it is imported from vue-class-component.

As for import ts files if you are not using vue-cli you'll need to setup webpack's resolve alias and also in your tsconfig.json and possibly will need to use tsconfig-paths

5 Comments

Its vue-class-component right? not vue-class-decorator.
Yes, you are right, I have confused and mixed it with 'vue-property-decorator'. Just updated. Thanks
Updated to match the folder structure that you've provided.
Yeah the folder structure and all is fine, once I build it this.test is displaying it as undefined.
Why do you have a default import of Vue in the .vue file and a non-default one in the mixin - and why is Vue imported at all in the component?
8

As of today there's 2 ways to use a mixin with Typescript/Vue:

  1. If your mixin holds just variables:
// mixins/title.ts
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class titleMixin extends Vue {
  public test: string = 'Hello, hello, hello'
}

// Page.vue
import { Component, Vue, Mixins } from 'vue-property-decorator'
import titleMixin from '@/mixins/title'

export default class Page extends Mixins(titleMixin) {
  private mounted(): void {
    console.log(this.test) // would print: Hello, hello, hello
  }
}
  1. If you're using lifecycle hooks:
// mixins/title.ts
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class titleMixin extends Vue {
  private mounted(): void {
    console.log('somevalue')
  }
}

// Page.vue
import { Component, Vue } from 'vue-property-decorator'
import titleMixin from '@/mixins/title'

@Component({
  mixins: [titleMixin]
})
export default class Page extends Vue {} // this will print console.log

This way it works for me. You can take a look at 'vue-class-component' package: https://github.com/vuejs/vue-class-component/blob/master/test/test.ts#L389

Comments

8

If you don't use vue-class-component (currently i'm not beacase it dosen't work well with setup/ composition api) you can use defineComponent as mixin and it's work in vue 3 with typescript

example of mixin:

yor_mixin.ts

import {defineComponent} from "vue"

interface MixinState{
    lastScrollPosition: number;
}

export default defineComponent({
    data(): MixinState{
        return {
            lastScrollPosition: 0,
        }
    },
    methods:{
        scrollDisable(){
            document.body.style.overflow = "hidden";
            this.lastScrollPosition = window.pageYOffset;
        },
        scrollEnable(){
            document.body.style.overflow = "auto";
            window.scrollTo(0, this.lastScrollPosition)
        }
    }
})

and component


<script lang="ts">
import {computed, defineComponent} from 'vue';
import {useStore, MUTATIONS} from "@/store";
import scrollDisableMixin from "@/mixins/scrollDisable";

export default defineComponent({
  mixins: [scrollDisableMixin],

  setup(){
    const store = useStore();
    return {
      expanded: computed(() => store.state.menu.expanded),
      toggle: () => store.commit(MUTATIONS.TOGGLE_MENU_MAIN),
    }
  },

  watch: {
    expanded: function(){
      this.expanded ? this.scrollDisable() :this.scrollEnable();
    }
  }

})

4 Comments

This is the most transparent solution when dealing with vue3 and the options API. Downsite is that the mixin does not know the components this anymore, so some refactoring could be needed.
@OnnovanderZee How mixin can know component this ? mixins don't know in which component it will be used so it know only its own this. Maybe i don't know something ( i started from vue 3 and before i was focused on react)
Mixins are merged in the component, as if vue performed Object.assign(component, mixin1, mixin2). So after the merge everything declared in the component is exposed to the mixin and vice versa. That includes the known risk of name collisions.
Ok. so you can declare the same variable in mixin and in component. In default merge strategy variables from component overwrite this from mixins. But you can write your own merge strategy and then overwrite options from component by this from mixins.docs. I know walk around but when you declare all your varialbes in mixins then is cleaner. At the end docs say that we should use composition api with hooks not mixins.
7

mixins.ts

import { Vue, Component } from "vue-property-decorator";

@Component
export default class Mixin extends Vue {
  public perfectScrollbarSetting: object = {
    maxScrollbarLength: 750
  };
  public widthClient: number = 0;
  public heightClient: number = 0;
}

file About.vue

<template>
</template>
<script lang="ts">
import { Vue, Component, Mixins } from "vue-property-decorator";
import { generalInfo } from "../../store/modules/general";
import Mixin from "../../mixin/mixins";
@Component({
  mixins: [Mixin]
})
export default class About extends Mixins(Mixin) {
  mounted() {
    console.log(this.widthClient) //it's work
  }

}
</script>

2 Comments

You only need mixins: [Mixin] or extends Mixins(Mixin)
You absolutely don't need declare mixins for each file. Use a way global by declare module for mixins.
0

As the disadvantage of mixins, why not try making a refactor of mixins to Vue3's setup: https://codesandbox.io/s/delicate-sea-0xmim?file=/src/components/HelloWorld.vue:2262-2495 Comp.vue

export default {
  name: "HelloWorld",
  setup() {
    const { test } = useGroup();
    console.log(test);
    return Object.assign({}, useGroup());
  },
  mounted() {
    this.compRef = this;
  },
  props: {
    msg: String,
  },
};

UseGroup.js

import { ref, nextTick, onMounted } from "vue";

export default () => {
  const compRef = ref(null);
  const test = "Hello World!";

  onMounted(() => {
    nextTick(() => {
      if (compRef.value) {
        const vm = compRef.value;
        console.log(vm.$el, vm.msg);
      }
    });
  });
  return {
    test,
    compRef
  };
};

Comments

0

This answer is for people who are willing to use vue-class-component (decorators)... What you have to do is just to import Options from 'vue-class-component' and add your mixin there.

Steps:

1- Create your mixin: In this example I'm creating a mixin to format the time (HH:MM:ss)

//Mixin
 export default {
   methods: {
     formatTime(date: string) {
       return new Date(date)
         .toTimeString()
         .replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1");
     },
   },
 };

2- Use the mixin in the component by adding it in the Option decorator.

   //Home.vue

    import { Options, Vue } from "vue-class-component";

    import formatTimeMixin from '../mixin/formatTime';

    <template>
       <h1> Time left: {{formatTime(timeLeft)}} </h1>
    </template>

    @Options({
      mixins: [formatTimeMixin],
    })
    export default class Home extends Vue {
       timeLeft = 10000;
    }

That's it, hope this help someone there!

1 Comment

I could be wrong but at this time, that @Options syntax is not available with vue-class-component. I think it's still in beta.
0

I wouldn't consider this a "mixin" anymore, but works..

SortHelperMixin.ts

import Vue from "vue";
export default Vue.extend({
  methods: {
    sortDate(a: string, b: string): number {
       ...
    },
  }
})

MyComponent.vue

import SortHelperMixin from '@/mixins/SortHelperMixin'
export default Vue.extend({
  name: "MyComponent",
  data() {
    return {
      sortHelperMixin: new SortHelperMixin(),
    };
  },
})

use

this.sortHelperMixin.sortDate(a, b)

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.