how to call vue i18n from non vue class? - vue.js

I am using the vue-i18n for a vuejs app.
All good, except how can I access the translations from a class that is not an extension of vue. Below is a simple class containing validation methods for element-ui to use eg:
import Validate from '#/services/Validate';
class FormValidate {
public password(rule: any, value: string, callback: any) {
callback(Validate.password(value) ? undefined : new Error('errors.passwordInvalid'));
}
}
export default new FormValidate();
Above the Error "errors.passwordInvalid" is a key for the translation file.
Within a typical component $t('errors.passwordInvalid') will return the human friendly string in the correct language.
How can i access the translation lib from this isolated class?

There is a thread on vue i18n github there or there. Basically the answer seems to be that you should separate i18n related code from your main.js file into i18n.js file. Which could look like:
export default new VueI18n({
// with all your settings here
})
Then import it as pure js file and t() method should work everywhere.

Related

Nuxt class-based services architecture (register globally; vs manual import)

In my Nuxt app I'm registering app services in a plugin file (e.g. /plugins/services.js) like this...
import FeatureOneService from '#/services/feature-one-service.js'
import FeatureTwoService from '#/services/feature-two-service.js'
import FeatureThreeService from '#/services/feature-three-service.js'
import FeatureFourService from '#/services/feature-four-service.js'
import FeatureFiveService from '#/services/feature-five-service.js'
export default (ctx, inject) => {
inject('feature1', new FeatureOneService(ctx))
inject('feature2', new FeatureTwoService(ctx))
inject('feature3', new FeatureThreeService(ctx))
inject('feature4', new FeatureFourService(ctx))
inject('feature5', new FeatureFiveService(ctx))
}
After doing this I can access any of my service on vue instance like this.$feature1.someMethod()
It works but I've once concern, that is, this approach loads all services globally. So whatever page the user visits all these services must be loaded.
Now I've 20+ such services in my app and this does not seem optimal approach to me.
The other approach I was wondering is to export a singleton instance within each service class and import this class instance in any component which needs that service.
So basically in my service class (e.g. feature-one-service.js) I would do like to do it like this..
export default new FeatureOneService() <---- I'm not sure how to pass nuxt instance in a .js file?
and import it my component where it is required like so...
import FeatureOneService from '#/services/feature-one-service.js'
What approach do you think is most feasible? if its the second one, then how to pass nuxt instance to my singleton class?
Yep, loading everything globally is not optimal in terms of performance.
You will need to either try to use JS files and pass down the Vue instance there.
Or use mixins, this is not optimal but it is pretty much the only solution in terms of reusability with Vue2.
Vue3 (composition API) brings composables, which is a far better approach regarding reusability (thing React hooks).
I've been struggling a lot with it and the only solution is probably to inject services to the global Vue instance at the component/page level during the initialisation (in created hook), another option is to do that in the middleware (or anywhere else where you have access to the nuxt context. Otherwise you won't be able to pass nuxt context to the service.
I usually set up services as classes, call them where necessary, and pass in the properties of the context which the class depends on as constructor arguments.
So for example, a basic MeiliSearchService class might look like:
export class MeilisearchService {
#client: MeiliSearch
constructor($config: NuxtRuntimeConfig) {
super()
this.#client = new MeiliSearch({
host: $config.services.meilisearch.host,
apiKey: $config.services.meilisearch.key
})
}
...
someMethod() {
let doSomething = this.#client.method()
...
}
...
}
Then wherever you need to use the service, just new up an instance (passing in whatever it needs) and make it available to the component.
data() {
const meiliSearchService = new MeiliSearchService(this.$config)
return {
meiliSearchService,
results,
...
}
},
methods: {
search(query) {
...
this.results = this.meiliSearchService.search(query)
...
}
}
As I'm sure you know, some context properties are only available in certain Nuxt life-cycle hooks. I find most of what I need is available everywhere, including $config, store, $route, $router etc.
Don't forget about Vue's reactivity when using this approach. For example, using a getter method on your service class will return the most recent value only when explicitly called. You can't, for example, stick the getter method in a computed() property and expect reactivty.
<div v-for='result in latestSearchResults'>
...
</div>
...
computed: {
latestSearchResults() {
return this.#client.getLatestResults()
}
}
Instead, call the method like:
methods: {
getLatestResults() {
return this.#client.getLatestResults()
}
}

How to acced to google object in vue 2?

I'm trying to use the google maps API at my vue2 project and I have tried some ways that have failed. After using the vue2googlemaps module and the node module from google I have decided to use the CDN directly and add it to the index page. My problem now is that to acced to the google object, for example, to create a Marker or something like that, I need to use this.marker = new window.google.maps.Marker() for example, but in the tutorials I have seen, everyone uses directly the google object and never uses that window. I can`t understand why it happens. It would be appreciated if someone shows me the correct way to import or use this library on google.
It's because your template's code is compiled and executed in your component instance (a.k.a vm) 's scope, not in the global (a.k.a. window) scope.
To use google directly in your template you could add the following computed:
computed: {
google: () => window.google
}
If your problem is not having google defined in the component's <script>, a simple solution is to add it as a const at the top:
import Vue from 'vue';
const google = window.google;
export default Vue.extend({
computed: {
google: () => google // also make it available in template, as `google`
}
})
An even more elegant solution is to teach webpack to get google from the window object whenever it's imported in any of your components:
vue.config.js:
module.exports = {
configureWebpack: {
externals: {
google: 'window.google'
}
}
}
This creates a google namespace in your webpack configuration so you can import from it in any of your components:
import google from 'google';
//...
computed: {
google: () => google // provide it to template, as `google`
}
Why do I say it's more elegant?
Because it decouples the component from the context and now you don't need to modify the component when used in different contexts (i.e: in a testing environment, which might not even use a browser, so it might not have a window object, but a global instead; all you'd have to do in this case is to define a google namespace in that environment and that's where your component will get its google object from; but you wouldn't have to tweak or mock any of the component's methods/properties).

Where to import file js in vue/nuxt

Usually I use a js code with functions to run on some events.
Now I am using nuxt.js and I wonder where to put this file or how to create a global method to use these functions in every component.
I could write the methods that I need inside every a specific component but after it wouldn't be usable outsite of it.
How to do that in vue/nuxt?
So one way to do it in vue.js is by using mixins, in nuxt you can also use mixins, then you should register them as plugins, but first:
Non global mixins
Create an extra folder for your mixins. For example in a /mixins/myMixin.js
export default {
methods: {
commonMethod() {
console.log('Hello')
}
}
}
Then import in a layout, page or component and add it via the mixins object:
<script>
import myMixin from '~/mixins/myMixin.js'
export default {
mixins: [myMixin]
}
</script>
Global mixins
For example in a new file plugins/mixinCommon.js:
import Vue from 'vue'
Vue.mixin({
methods: {
commonMethod() {}
}
})
Include the file in nuxt.config.js like that:
plugins: ['~/plugins/mixinCommon']
After that you would have the method everywhere available and call it there with this.commonMethod(). But here an advice from the vue.js docs:
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.js 2 how use components in ES2015 webpack

I am trying to use vue-components in a webpack Typescript project but it doesn't seem to be working. I don't get any errors during the build and run, but the component HTML is never inserted into the output - I can just see the HTML source of the component instead i.e. .
My project is an ES2015 using Vue2 in VS.Net 2017. My component looks like this:
import Vue from 'vue'
import Component from 'vue-class-component'
// The #Component decorator indicates the class is a Vue component
#Component({
// All component options are allowed in here
template: '<button #click="onClick">Click!</button>'
})
export default class MyHeader extends Vue {
// Initial data can be declared as instance properties
message: string = 'Hello!'
// Component methods can be declared as instance methods
onClick(): void {
window.alert(this.message)
}
}
I have tried the official reference guide to register the component and use it. When I look at the vue-component example, it uses the same format as my project so I added the markup and properties to my Typescript class definition:
import Vue from 'vue';
import Component from 'vue-class-component';
import MyHeader from './MyHeader';
#Component({
components: {
MyHeader
}
})
export default class GetDataComponent extends Vue {
<...rest of class...>
}
but in my project the "components:" section is squiggly-underline-red with the message:
Object literal may only specify known properties, but 'components'
does not exist in type 'VueClass'. Did you mean to write
'component'?
Every example I have seen with vue-component (such as this one) uses the "components:" option in the #Component to register and use their Vue component, but in my project it doesn't seem to like it. I have also tried global registration of the component (such as this one) which includes the line:
// Register the component globally
Vue.component(my-header', MyHeader)`
but in that case I get an error like this:
Type 'typeof MyHeader' is not assignable to type 'AsyncComponent'
The Vue file works (without the Component added) and all content is rendered correctly. It's getting the Component included that doesn't work - I either get Design-time errors per above, or nothing appears in the output at all.
Is my import wrong? Or the format of the #Component? I get the feeling I am doing something that is very basic, very wrong...

Custom js library(scrollMonitor) inside main Vue instance to be shared with inner components

This is Vue.js question, generally I'm trying to use 'scrollMonitor' function inside of my .vue instance(imported via main.js) but it gives me a typical 'this.scrollMonitor is not a function' error
mounted () {
let watcher = this.$scrollMonitor(this.$refs.nicer)
}
In main.js ScrollMonitor library seems to be properly imported(console shows what's expected):
import scrollMonitor from 'scrollmonitor'
Vue.use(scrollMonitor)
console.log(scrollMonitor)
Again main goal is using scrollMonitor functionality inside of .vue file(in vue component instance). Sorry if I'm missing something silly here - I'm already using some other libraries like Vue-Resource in that file so issue is not in 'filepath' but rather in the way I'm using scrollMonitor functionality, any help is much appreciated, thank you !
For those who are still looking: there is a way of adding plain js libraries to the main.js and then using them with ease globally in inner components(this is not about mixins):
import scrollmonitor from 'scrollmonitor'
Object.defineProperty(Vue.prototype, '$scrollmonitor', {
get() {return this.$root.scrollmonitor}
})
also it should be added to main Vue data object:
data () {
return { scrollmonitor }
},
And then it can be used within mounted() callback (not created() one) inside of the component itself, with scrollmonitor it may look like this(in my specific case the template had a div with ref="nicer" attribute, 'create' is a method specific to the library api):
mounted () {
this.$scrollmonitor.create(this.$refs.nicer)
}
Hooray, I hope someone may find this useful as I did!
Are you using a plain javascript library and trying to Vue.use it? That won't really work. Vue.use will only work with plugins designed to work with Vue. Import the library into the component that needs and and just use it there.
scrollMonitor(this.$refs.nicer)