Vue JS - custom properties on vue component gives TS errors - javascript

I have created some Vue middleware and I am trying to add a custom property to one of my components in Vue like so:
middleware.js:
import { VueConstructor } from 'vue/types';
function eventPlugin(vue: VueConstructor): void {
const Socket = new someClass();
Object.defineProperties(vue.prototype, {
$socket: {
get: function get() {
return Socket;
},
},
});
vue.$socket = Socket;
}
myComponent.js
const MyComponent = Vue.extend({
name: 'MyComponent',
$socket: {
event(data: any) {
}
},
methods: {
MyMethod() {
}
}
})
app.js
import Vue from 'vue';
import eventPlugin from './middleware.js';
import MyComponent from './myComponent.js'
Vue.use(eventPlugin);
export default new Vue({
render: (h) => h(MyComponent),
}).$mount('#app');
The custom property I am trying to add here is obviously socket. The problem is when I add it I get typescript errors:
Object literal may only specify known properties, and 'socket' does
not exist in type 'ComponentOptions<Vue, DefaultData,
DefaultMethods, DefaultComputed, PropsDefinition<Record<string,
any>>, Record<...>>'.
As you can see in middleware.js I have tried defining the property there so I am not sure why I am receiving the error?

When adding instance properties or component options, you also need to augment the existing type declarations.
Based on Augmenting Types for Use with Plugins (Vue 2):
To type-hint the $socket instance property:
declare module 'vue/types/vue' {
interface VueConstructor {
$socket: string
}
}
export {}
To type-hint the $socket component option:
import Vue from 'vue'
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
$socket?: string
}
}
export {}
The type declarations above should go in a .d.ts file in your src directory. If using VS Code, any new .d.ts files might require restarting VS Code to load.

Related

Use a certain object instance in multiple modules without global variables [duplicate]

Will the snippets below produce new instance every time it's imported?
// 1st implementation
class ConnectionManager {
...
}
export default new ConnectionManager();
// 2nd implementation
class ConnectionManager {
...
}
const connectionManager = new ConnectionManager();
export default connectionManager;
If yes, how can I get the same instance in every import?
ES6 modules follow single instance pattern. That is, the instance is created when the module is loaded.
Here is an article about it.
// File: yolo.js
class Yolo {}
export let yolo = new Yolo();
// File: laser.js
import { yolo } from "./yolo.js";
// yolo is a single instance of Yolo class
// File: cat.js
import { yolo } from "./yolo.js";
// same yolo as in laster.js
It should be the same.
The following example uses both the implementations, imports them into 2 different files, and imports them all into single index file. Everytime an instance is created, we generate a random value for the class, and log its creation.
// ConnectionManagerImpl1.ts
class ConnectionManagerImpl1 {
public value;
constructor() {
this.value = Math.random().toString(36).substring(7);
console.log(`New ConnectionManagerImpl1 instance created: ${this.value}`)
}
}
export default new ConnectionManagerImpl1();
// ConnectionManagerImpl2.ts
class ConnectionManagerImpl2 {
public value;
constructor() {
this.value = Math.random().toString(36).substring(7);
console.log(`New ConnectionManagerImpl2 instance created: ${this.value}`)
}
}
const connectionManagerImpl2 = new ConnectionManagerImpl2();
export default connectionManagerImpl2;
// import1.ts
import connectionManagerImpl1 from './ConnectionManagerImpl1';
import connectionManagerImpl2 from './ConnectionManagerImpl2';
export { connectionManagerImpl1, connectionManagerImpl2 };
// import2.ts
import connectionManagerImpl1 from './ConnectionManagerImpl1';
import connectionManagerImpl2 from './ConnectionManagerImpl2';
export { connectionManagerImpl1, connectionManagerImpl2 };
// index.ts
import * as import1 from './import1';
import * as import2 from './import2';
console.log(import1)
console.log(import2)
console.log("Done")
Ran the above setup using tsc --module 'commonjs' * && node index.js
Output:
New ConnectionManagerImpl1 instance created: ddt3re
New ConnectionManagerImpl2 instance created: uv5z6
{ connectionManagerImpl1: ConnectionManagerImpl1 { value: 'ddt3re' },
connectionManagerImpl2: ConnectionManagerImpl2 { value: 'uv5z6' } }
{ connectionManagerImpl1: ConnectionManagerImpl1 { value: 'ddt3re' },
connectionManagerImpl2: ConnectionManagerImpl2 { value: 'uv5z6' } }
Done
As you can see, only 1 instance of ConnectionManagerImpl1 and ConnectionManagerImpl2 were created. So, both the implementation should create only 1 instance.
The export statement is used when creating JavaScript modules to export functions, objects, or primitive values from the module so they can be used by other programs with the import statement.
There are two different types of export, named and default. You can have multiple named exports per module but only one default export.
export default class ConnectionManager { .. }
Or
class ConnectionManager {
...
}
export default connectionManager;

axios property get of undefined

I have a class inside src/lib/lib.ts
export default class LibClass implements LibInterface {
someFunc () {
...
const httpResponse = await (this as any).$axios.$get(url)
return httpResponse.data
}
}
and inject it on the component via
import { Inject } from 'inversify-props'
import LibInterface from '#/lib/lib'
#Component
export default class Test extends Vue {
#Inject()
private libCLass!: LibInterface
}
and Im getting TypeError: Cannot read property '$get' of undefined
I already defined the #nuxtjs/axios on tsconfig.json
"types": [
"#nuxtjs/axios",
]
I used $axios on LibClass to be able to separate my axios calls and just call it on my components using dependency injection (inversify.js)
any idea on how can I fix this?

Error in Vue i18n with TypeScript: "Property '$t' does not exist on type 'VueConstructor'. " . How can I fix it?

In project some common function are in separate .ts files.
How can I use i18 in that cases:
// for i18n
import Vue from 'vue'
declare module 'vue/types/vue' {
interface VueConstructor {
$t: any
}
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
t?: any
}
}
(()=>{
const test = Vue.$t('auth.title');
console.log( test )
})()
Return an error:
Property '$t' does not exist on type 'VueConstructor<Vue>"
How can I fix it?
we can achieve the same like below
Step 1: create a separate index.ts file inside a i18n folder (you can do it your own way - root level or any where in your app)
i18n/index.ts
import Vue from 'vue';
import VueI18n from 'vue-i18n';
// register i18n module
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'nb-NO', //if you need get the browser language use following "window.navigator.language"
fallbackLocale: 'en',
messages: {en, no},
silentTranslationWarn: true
})
const translate = (key: string) => {
if (!key) {
return '';
}
return i18n.t(key);
};
export { i18n, translate}; //export above method
Step 2: make sure to use(import) above in main.ts
main.ts
import { i18n } from '#/i18n';
new Vue({ i18n, render: h => h(app) }).$mount('#app')
after above configuration we should be able to use translation in any place that we want in our application
Step 3: How to use it in .ts and .vue files
// first import it into the file
import { translate, i18n } from '#/i18n';
//this is how we can use translation inside a html if we need
<template>
<h1>{{'sample text' | translate}}</h1>
</template>
//this is how we can use translation inside a .ts or .vue files
<script lang='ts'>
//normal scenario
testFunc(){
let test = `${translate('sample text')}`;
console.log(test );
}
//in your case it should be like below
(()=>{
const test = `${translate('auth.title')}`;
console.log( test )
})()
</script>
I hope that this will help you to resolve your issue.

Typescript migration: Javascript "namespace" with multiple objects

I'm migrating some old js to ts. The file is of form (function implementations omitted for clarity):
// component.js
const Component = {}; // 'namespace' for components
Component.Base = function() {}
Component.A = function() {} // extends Base
Component.A.prototype.doStuffA = function() {}
Component.B = function() {} // extends Base
Component.B.prototype.doStuffB = function() {}
Component.C = function() {} // extends Base
// ... 100 other components, 2000 lines of code
export default Component;
In js, to use the file, I can do:
import Component from './component';
// 1. Instantiate one component
const compA = new Component.A();
// 2. or create multiple components
const threeComps = ['A', 'B', 'C'].map(name => new Component[name]() );
But in ts, I cannot even instantiate one component:
import Component from './component';
const compA: Component.A = new Component.A();
// tsc Cannot find namespace 'Component'
Question: What is the (quick) way to convert component.js into valid typescript, preferably keeping as many type-checks available as possible such
that
const compA: Component.A = new Component.B()
will be flagged as an error by the compiler.
I tried appending the following to the end of file:
namespace Component {
interface A {};
interface B {};
interface C {};
}
This seems to compile into correct javascript, but I would have to add all properties into interfaces. Seems tedious and violation of DRY-principle.
If you are going to migrate to TypeScript, you could immediately take advantage of the class syntax in your component.ts file:
export class Base {
}
export class A {
doStuffA() {}
}
export class B {
doStuffB() {}
}
export class C extends Base {
}
You can consume it using an import alias:
import * as Component from './component';
const a = new Component.A();
Or you can be selective with what you import:
import { A } from './component';
const a = new A();
Export Default / Modules Mixed With Namespaces
On the whole, the experts are saying that export default is a bad thing and that you shouldn't mix modules and namespaces.
You can do it, if you feel you must. here is the example with a namespace / default:
namespace Component {
export class Base {
}
export class A {
doStuffA() {}
}
export class B {
doStuffB() {}
}
export class C extends Base {
}
}
export default Component;
And the use:
import Component from './component';
const a = new Component.A();

Setting up a utility class and using it within a vue component

A vue application I am working on currently has lots of code redundancies relating to date functions. In an effort to reduce these redundancies, I'd like to create a utility class as shown below, import it and set it to a Vue data property within the component, so I can call the date functions within it.
I am not certain on the best way to implement this. The current implementation results in an error saying TypeError: this.dates is undefined and my goal is not only to resolve this error but create/utilize the class in the Vue environment using best standards.
Importing utility class
import Dates from "./utility/Dates";
...
Component
const contactEditView = Vue.component('contact-edit-view', {
data() {
return {
contact: this.myContact
dates: Dates
}
},
...
Dates.js
export default {
dateSmall(date) {
return moment(date).format('L');
},
dateMedium(date) {
return moment(date).format('lll');
},
dateLarge(date) {
return moment(date).format('LLL');
}
};
View
Date of Birth: {{ dates.dateMedium(contact.dob) }}
My suggestion for this is to use a plugin option in Vue. About Vue plugin
So you will crate a new folder called services, add file yourCustomDateFormater.js:
const dateFormater = {}
dateFormater.install = function (Vue, options) {
Vue.prototype.$dateSmall = (value) => {
return moment(date).format('L')
}
Vue.prototype.$dateMedium = (value) => {
return moment(date).format('lll')
}
}
In main.js:
import YourCustomDateFormater from './services/yourCustomDateFormater'
Vue.use(YourCustomDateFormater)
And you can use it anywhere, like this:
this.$dateSmall(yourValue)
Or, if you want to use mixin. Read more about mixin
Create a new file dateFormater.js
export default {
methods: {
callMethod () {
console.log('my method')
}
}
}
Your component:
import dateFormater from '../services/dateFormater'
export default {
mixins: [dateFormater],
mounted () {
this.callMethod() // Call your function
}
}
Note: "Use global mixins sparsely and carefully, because it affects every single Vue instance created, including third party components. In most cases, you should only use it for custom option handling like demonstrated in the example above. It’s also a good idea to ship them as Plugins to avoid duplicate application." - Vue documentation
dateUtilsjs
import moment from 'moment-timezone'
function formatDateTime(date) {
return moment.utc(date).format("M/D/yyyy h:mm A")
}
export { formatDateTime }
Component JS
...
import { formatDateTime } from '../utils/dateUtils'
...
methods: {
formatDateTime,
}
Used within component
{{ formatDateTime(date) }}

Categories