A set of TypeScript decorators for make the work with Vue.js easiest.
Switch branches/tags
Nothing to show
Clone or download
InDIOS Added ability of use helper methods in filters.
Filter classes can now have several methods, only valid Vue filter methods are taken for built the filter and bound to the class instance. Because filters are not bound to the component instance,'this' is undefined inside it, this can be do it.
Latest commit f77a287 Dec 14, 2017

README.md

vue-ts-decorate

A set of TypeScript decorators for make the work with Vue.js easiest.

Note: This project take some ideas and examples of the projects av-ts, vue-ts and vue-typescript.

The Why?

The projects mentioned in the note above are very good but, for some reasons not include some decorators that i believe are important too, further are incompatible with Vue 2.x.x.

Features

  • Compatibility with Vue version 1.x.x and 2.x.x.
  • @Component: Create a Vue component: Vue.extend and Vue.component.
    • @Prop: Create a component property.
    • @On: Create a component on event listener. --> Work only with Vue 1.x.x
    • @Once: Create a component once event listener. --> Work only with Vue 1.x.x
    • @Watch: Create a component watcher.
    • @Getter: Create a component getter from state property. --> Work only with Vuex >=0.6.3 <0.7.0
    • @Action: Create a component action. --> Work only with Vuex >=0.6.3 <0.7.0
  • @Directive: Create a Vue directive, local or global.
  • @Filter: Create a Vue filter, local or global.
  • @Mixin: Create a mixin object.

Use with VueRouter and Vuex 2.x.x

  • VueRouter methods beforeRouteEnter and beforeRouteLeave are supported as class methods (without decoration).
  • With Vuex, for better usability and performance, use map* (* means State, Getters, Actions and Mutations) Vuex module methods in component options with the object spread operator.

Configuration

Follow the Vue configuration for Webpack and Browserify related in Vue Doc

See all the examples below:

Usage

// class only
@Component() 
  or
@Component(options)

Basic Component declaration

import * as Vue from 'vue';
import { Component } from 'vue-ts-decorate';
// or use
// import Component from 'vue-ts-decorate/component';

@Component()
class MyComponent extends Vue {
  // No annotated properties are added to `data` property.
  someData: string = '';

  // methods named as instance hooks are added like hooks
  mounted() {
    this.someData = 'Hello!, MyComponent.';
  }

  // No annotated methods ara added to `methods` property.
  someMethod() {
    // ...
  }
}

new MyComponent().$mount('#app');

The code above it is the same that:

NOTE: All Vue Router hooks are also supported as classes methods, except the activate hook because it has the same name as the Vue components hook in the version 1.x.x. In version 2.x.x only beforeRouteEnter and beforeRouteLeave are added as hooks.

let MyComponent = Vue.extend({
  data: {
    someData: ''
  },
  mounted() {
    this.someData = 'Hello!, MyComponent.';
  },
  methods: {
    someMethod() {
      // ...
    }
  }
});

new MyComponent().$mount('#app')

The @Component decorator receives an object as an optional parameter with the same properties of a Vue component, to these properties is added the componentTag property which is a string to specify the name of the tag will have the component. If it is present, the component will be a global, otherwise will be local. el property are deprecate. To do a mounted component see below:

Example

// Mounted Component
@Component()
class MountedComp extends Vue {
  // ...
}
new MountedComp().$mount('#app');

// Global Component
@Component({ componentTag: 'globalComp' })
class GlobalComp {
  // ...
}

// local Component
@Component()
class LocalComp {
  // ...
}

The example above it is the same that:

// Mounted Component
let MountedComp = Vue.extend({
  // ...
});
new MountedComp().$mount('#app');

// Global Component
Vue.component('global-comp', {
  // ...
});

// local Component
Vue.extend({
  name: 'LocalComp'
  // ...
});

Yes, in the componentTag you can use the camelCase style.

Components can take other components too, in the options of @Component

// Child Component
@Component()
class ChildComp {
  // ...
}

// Parent Component
@Component({ 
  components: { ChildComp }
})
class ParentComp {
  // ...
}

Props, Events and Watchers

// properties or methods only
@Prop()
  or
@Prop(options)
// methods only
@On(eventName)
@Once(eventName)
// methods only
@Watch(propertyName)
  or
@Watch(propertyName, options)
import * as Vue from 'vue';
import { Component, Prop, Watch, On, Once } from 'vue-ts-decorate';

@Component()
class MyComponent extends Vue { // the class extends of Vue just to get intellitSense inside the methods
  someVar: string = 'Hello!';

  @Prop({
    // all property options
  })
  someProp:string;

  @Prop({
    type: String
  })
  someDefaultProp:string = 'some default value'; 

  // vue-ts-decorate makes sure to deep clone default values for array and object types
  @Prop()
  someObjProp: { some_default: string } = { some_default: 'value' };

  // defined functions decorated with prop are treated as the default value
  @Prop()
  someFuncProp() { 
    // ...
  }

  someMethod() {
    // ...
  }

  // Watch accept a string who would be the property name
  // and an options object as second parameter
  @Watch('someVar')
  someVarWatcher(newVal, oldVal) {
    // ...
  }

  @On('eventToEmit') // if param is omited then, the function name is used as a event name
  someEvent() {
    // ...
  }

  @Once('eventToEmitOnce') // if param is omited then, the function name is used as a event name
  someEventOnce() {
    // ...
  }
}

It is equivalent to

Vue.extend({
  name: 'MyComponent',
  data() {
    return {
      someVar: 'Hello!'
    };
  },
  props: {
    someProp: null,
    someDefaultProp: {
      type: String,
      default: 'some default value'
    },
    someObjProp: {
      default() {
        return { 
          some_default: 'value' 
        };
      }
    },
    someFuncProp: {
      type: Function,
      default() {
        // ...
      }
    }
  },
  methods: {
    someMethod() {
      // ...
    }
  },
  watchs: {
    someVar(newVal, oldVal) {
      // ...
    }
  },
  events: {
    eventToEmit() {
      // ...
    },
    eventToEmitOnce() {
      // ...
    }
  }
});

Support for Vuex 1.x.x

NOTE: In version 2.x.x this decorators fail silent. Please see the Vuex 2 documentation for how to use the maps methods.

import { someAction } from '../actions';
import { Component, Getter, Action } from 'vue-ts-decorate';

@Component()
class MyComponent {
  // Getter accept a string with the properties of the state
  // in the format: 'foo.bar.baz'.
  // NOTE: Omit the `state` in the string because it is added internaly.
  @Getter('app.counter')
  counter:string;

  // or just put the function as a parameter of the Getter.
  @Getter((state) => state.app.hello + ' World')
  someProp:string; 

  // Action accept a function as parameter.
  // NOTE: I use a property with type `(params) => void` to tell the compiler that
  // the property is a function and get intellitSense;
  @Action(someAction)
  someFuncProp: (someParam) => void;

  // ...
}

The equivalent in javascript is

Vue.extend({
  name: 'MyComponent',
  vuex: {
    getters: {
      someProp(state) {
        return state.app.counter;
      }
    },
    actions: {
      someFuncProp: someAction
    }
  }
  // ...
});

Directive

import { Component, Directive, VueDirective } from 'vue-ts-decorate';

// The property `name` in directive's option is required to make a safe uglification.
// This is a global directive.
// The methods `bind` and `unbind` are optionals
// just the `update` method is required.
// NOTE: To get intellitSense and fix possible errors inside the methods,
// the directive class must be extends of `VueDirective` abstract class
@Directive({ name: 'myGlobalDirective', /* other directive options */ })
class MyGlobalDirective extends VueDirective {
  bind() {
    // ...
  }

  update() {
    // ...
  }

  unbind() {
    // ...
  }
}

// With the `local` boolean property
// you can declare a local directive.
// NOTE: Directives classes just with `update` method
// are converted to functions, otherwise will be an object.
@Directive({ name: 'myLocalDirective', local: true, /* other directive options */ })
class MyLocalDirective extends VueDirective {
  update() {
    // ...
  }
}

// and in the component
@Component({ 
  /* other options */,
  // the name of the directives 
  // is converte in kebab-case internaly as well
  directives: { MyLocalDirective }
})
class MyComponent {
  // ...
}

The equivalent in javascript

Vue.directive('my-global-directive', {
  // options go here
  bind: function() {
    // ...
  },
  update: function() {
    // ...
  },
  unbind: function() {
    // ...
  }
});

Vue.extend({
  // options go here
  directives: {
    'my-local-directive': function() {
      // ...
    }
  }
});

Filters

@Filter decorator receives two parameters:

The first is required and is the name of the filter, and the second is a boolean that indicate if the filter is global or not.

import { Component, Filter } from 'vue-ts-decorate';

// This is a global filter.
// The filter class just need a method called `filter`
@Filter('myGlobalFilter')
class MyGlobalFilter {
  filter(value: any, ...params: any[]) {
    // ...
  }
}

// This is a local filter.
@Filter('myLocalFilter', true)
class MyLocalFilter {
  filter(value: any, ...params: any[]) {
    // ...
  }
}

// and in the component
@Component({ 
  /* other options */
  filters: { MyLocalFilter }
})
class MyComponent {
  // ...
}

// This is a tow way binding filter.
@Filter('myTowWayFilter')
class MyTowWayFilter {
  read(value: any) {
    // ...
  }

  write(value: any, ...params: any[]) {
    // ...
  }
}

and in javascript

Vue.filter('myGlobalFilter', function(value, params) {
  // ...
});

Vue.extend({
	name: 'MyComponent',
  filters: {
    myLocalFilter: function(value, params){
      // ...
    }
  }
  // and other options...
});

Vue.filter('myTowWayFilter', {
  read: function(value) {
		// ...
	},
  write: function(value, params) {
		// ...
	}
});

Mixins

import { Component, Mixin } from 'vue-ts-decorate';

// Global Mixins are availables with the the
// `global` property set to true, otherwise 
// (can be omited) mixin is local.
@Mixin({ global: true })
class GlobalMixin {
  someGlobalProp: string = 'some value';

  someGlobalMethod() {
    // ...
  }
}

// Mixins are classes with the same behavior
// of components but are not converted in one.
@Mixin()
class ParentMixin {
  someProp: string = 'some value';

  someMethod() {
    // ...
  }
}

// All decorators and options are the same as components.
@Mixin({ /* options go here */ })
class ChildMixin extends ParentMixin { // Mixins can extends of other Mixins.
  someChildProp: string = 'some value';

  someChildMethod() {
    // ...
  }
}

@Mixin()
class BrotherMixin extends ParentMixin {
  someBrotherProp: string = 'some value';

  someBrotherMethod() {
    // ...
  }
}

// To simulate the multiple inheritance,
// we can do like describe the TypeScript Mixins Docs,
// to get intellitSense and fix possible errors.
// https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Mixins.md
@Component({ mixins: [BrotherMixin] })
class MyComponent extends ChildMixin implements BrotherMixin {

  someBrotherProp: string;
  someBrotherMethod: () => void;
  // ...

}

in javascript

Vue.mixin({
  data: function() {
    return { 
      someGlobalProp: 'some value' 
    };
  },
  methods: {
    someGlobalMethod: function() {
      // ...
    }
  }
});

var ChildMixin = {
  data: {
    someProp:'some value',
    someChildProp: 'some value'
  },
  methods: {
    someMethod: function() {
      // ...
    },
    someChildMethod: function() {
      // ...
    }
  }
}

var BrotherMixin = {
  data: {
    someProp:'some value',
    someBrotherProp: 'some value'
  },
  methods: {
    someMethod: function() {
      // ...
    },
    someBrotherMethod: function() {
      // ...
    }
  }
}

Vue.extend({ 
  // ...
  mixins: [ChildMixin, BrotherMixin] 
});

Note on using new with component classes:

You can call a class with the new operator only if componentTag property is not set or present in the @Component decorator options.

TO-DO

  • Add examples.
  • Make more tests.
  • Document how use style property for scoped style and how it works.

Contributions

Any contribution are accepted if going in the good way and, of course, feel free to report an issues too, if something going bad.

License

MIT