Conditional import in Vue component - javascript

I have a root instance that has several CustomVideo-components in it (amongst a bunch of other components). The CustomVideo-component implements VideoJS, but it's not on all pages that there is a CustomVideo-component, so I don't want to import VideoJS globally. Here is an example of components on a page:
App.js
|
|-- CustomVideo
|-- FooComponent
|-- CustomVideo
|-- BarComponent
|-- CustomVideo
In the top of CustomVideo, I import VideoJS, like so:
import videojs from 'video.js';
import abLoopPlugin from 'videojs-abloop'
export default {
name: "FeaturedVideoPlayer",
props: {
videoUrl: String
}
mounted() {
let videoOptions = {
sources: [
{
src: this.videoUrl,
type: "video/mp4"
}
],
plugins: {
abLoopPlugin: {
'enabled': true
}
}
};
this.player = videojs(this.$refs.featuredVideoPlayer, videoOptions, function onPlayerReady() {});
}
But if there are more than one CustomVideo, then I get a console warning:
VIDEOJS: WARN: A plugin named "abLoopPlugin" already exists. You may want to avoid re-registering plugins!
I tried looking into conditional imports, but it doesn't seem like it's the way to do it.
Even if I try and import it in app.js, even though I would rather import it CustomVideo, then I get another console error:
Attempt
import abLoopPlugin from 'videojs-abloop'
Vue.use( abLoopPlugin );
Then I get the error:
Uncaught TypeError: Cannot read property 'registerPlugin' of undefined
How do I ensure that a plugin is registered only once?

Check videojs.getPlugins().abLoopPlugin
videojs.getPlugins() returns a symbol table of all loaded plugin names. You could simply check that abLoopPlugin is not in that table before loading it in your component:
import videojs from 'video.js'
if (!videojs.getPlugins().abLoopPlugin) {
abLoopPlugin(window, videojs)
}
Await $nextTick before using ref
You'll notice that your videos are initially not visible in your specified <video> element. This is because the ref is undefined when you pass it to videojs in mounted().
The ref docs state:
An important note about the ref registration timing: because the refs themselves are created as a result of the render function, you cannot access them on the initial render - they don’t exist yet!
The solution is to wait until the $nextTick():
async mounted() {
// this.$refs.featuredVideoPlayer is undefined here
await this.$nextTick()
// this.$refs.featuredVideoPlayer is the <video> here
this.player = videojs(this.$refs.featuredVideoPlayer)
}

Try creating instance of abLoopPlugin. I followed the same approach for vue-i18n and other plugins to use globally. Something like:
import AbLoopPlugin from 'videojs-abloop'
Vue.use(AbLoopPlugin) // CHECK IF REGISTER PLUGIN ERROR PERSIST
const abLoopPlugin = new AbLoopPlugin({
---your settings---
})
export default abLoopPlugin

Related

Set a class to prototype in NuxtJS Vue

How to set a class to prototype correctly in Vue NuxtJS?
I create plugin
nuxt.config.js
plugins: [
{ src: "~/plugins/global.js" },
],
global.js
import Vue from "vue";
import CustomStore from "devextreme/data/custom_store";
//try set in prototype
Vue.use(CustomStore)
have error
A class must be instantiated using the 'new'
I understand that this is not correct, but I can not find anywhere how to initialize it
Vue.use(new CustomStore());
no error but how call?
I want to use something like this in my component
this.dataSource = this.$CustomStore({ ///... settings...// })
I assume that CustomStore is a function, so you can try using Nuxt.js inject() method. This method will make your functions or values available across your app.
~/plugins/global.js
export default ({ app }, inject) => {
// Inject $CustomStore() in Vue, context and store.
inject('CustomStore', CustomStore)
}
then you can use it across your app components.
your component
mounted() {
this.$CustomStore()
},
reference https://nuxtjs.org/docs/2.x/directory-structure/plugins#inject-in-root--context

Access this.$root in Vue.js 3 setup()

In Vue 2, you can access this.$root inside the created hook. In Vue 3, everything that would have gone inside the created hook now goes in setup().
In setup() we don't have access to this, so, how can we access anything on the root instance?
Say, I set a property on the root instance:
const app = createApp(App).mount('#app');
app.$appName = 'Vue3';
I can access this from mounted() with this.$root.$appName, how can I do this in setup()?
UPDATE
I can access it if I import it:
import app from '#/main';
...
setup() {
console.log(app.$appName) // Vue3
But, this is a hassle if I have to do this for every file.
UPDATE 2
Another workaround is to use provide() inside App.vue and then inject() in any other components:
setup() {
provide('$appName', 'Vue3')
setup() {
inject('$appName') // Vue3
You could define global property in vue 3 :
app.config.globalProperties.appName= 'vue3'
With setup (composition api) you could use getcurrentinstance to get access to that property:
import { getCurrentInstance } from 'vue'
...
setup() {
const app= getCurrentInstance()
console.log(app.appContext.config.globalProperties.appName)
Since you're still able to use the options api you could simply do :
mounted(){
console.log(this.appName)
}
It seems you need provide / inject. In your App.vue:
import { provide } from 'vue';
export default {
setup() {
provide('appName', 'vue3')
}
}
Or provide it with your app:
const app = createApp(App);
app.mount('#app');
app.provide('appName', 'Vue3');
And then in any child component where you want to access this variable, inject it:
import { inject } from 'vue'
export default {
setup() {
const appName = inject('appName');
}
}
If all you want is to replace {{ appName }} in any any template with 'Vue 3' (string), without having to import anything, the cleanest way would be using config.globalProperties, as suggested by other answers:
const app = createApp(App).mount('#app');
app.config.globalProperties.appName = 'Vue 3'
However, you should try not to overuse this pattern. It goes against the reusability and modularization principles which drove the development of Composition API.
The main reason why you should avoid polluting globalProperties is because it serves as pollution field across Vue3 apps, so many plugin devs might decide to provide their plugin instance using it. (Obviously, nobody will ever name a plugin appName, so you run no risk in this particular case).
The recommended alternative to globalization is exporting a useStuff() function.
In your case:
export function useAppName () { return 'Vue 3' }
// or even:
export const useAppName = () => 'Vue 3'
In any component:
import { useAppName } from '#/path/to/function'
setup () {
const appName = useAppName()
return {
appName // make it available in template and hooks
}
}
The advantages:
it uses the Composition API naming convention
when sharing something more complex than a primitive (could be a module, a set of functions, a service, etc...) all types are inferred, out of the box. This is particularly useful in setup() functions.
you only expose and scope your stuff where you need it exposed, not in every single component of your app. Another advantage is: if you only need it in setup() function, you don't have to expose it to template or hooks.
Example usage with a random (but real) plugin:
Create a plugin file (i.e: /plugins/gsap.ts):
import gsap from 'gsap'
import ScrollToPlugin from 'gsap/ScrollToPlugin'
// configure the plugin globally
gsap.registerPlugin(ScrollToPlugin)
export function useGsap () {
return gsap
}
In any component:
import { defineComponent } from 'vue'
import { useGsap } from '#/plugins/gsap'
export defineComponent({
setup () {
const gsap = useGsap()
// gsap here is typed correctly (if the plugin has typings)
// no need for casting
return {
gsap // optionally provide it to hooks and template
} // if needed outside setup()
}
})
For anyone wondering how they can simply access this inside setup(), one way is to set this to a memoized variable in the created() hook and use nextTick() to access it:
const app = createApp(App);
app.config.globalProperties.$appName = 'Hello!';
<script>
import { nextTick } from 'vue';
let self;
export default {
name: 'HelloWorld',
setup() {
nextTick(() => console.log(self.$appName)); // 'Hello!'
},
created() {
self = this;
},
};
</script>
#Psidom's answer is better practice in my opinion, but, this is just another way.

Vue / Vuex. Vuex store properties not accessible from my component

I have a vue app, and a component. The component is registered and rendering just fine. I just imported vuex to help with state management. I am using typescript and vue-class-decorator for better type safety.
My app look like so: .ts
// load vuex stores
let store = require('./store/store.ts');
// register components here...
Vue.component('my-component', require('./components/MyComponent.vue'));
// initialize vue applications here...
const app = new Vue({
el: '#app',
store
});
I can console.log the store and see that store is indeed required correctly.
Here's my store: .ts
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
active: false,
},
getters: {
isActive: state => state.active
}
});
Here's my component: .ts
import { Vue, Component, Prop } from 'vue-property-decorator'
import axios from 'axios'
#Component
export default class MyComponent extends Vue {
#Prop(String) route?: string
resent: boolean = false
loading: boolean = false
// constructor
mounted (): void {
if (!this.route) {
throw new Error("The route property is missing");
}
}
get myActiveState (this :Vue): any {
console.log(this.$store);
return this.$store.getters.isActive;
}
}
It doesn't matter what I try, I cannot access the stores state property. I can console log this.$store and I do indeed get the correct store, same if I put a breakpoint in and inspect I can directly access the active property, but if I try and console log this.store.state.active or this.store.getters.isActive, then I get an error.
[Vue warn]: Error in render: "TypeError: Cannot read property 'isActive' of undefined"
I can put a breakpoint in and inspect the console to double check the contents of each property. Everything looks good. BUT i cannot access the property with $store.state.active I have to do $store.default.state.active.
What is going on here? Why can I not access the state when it's coded? Additionally trying to code this.$store.default.state.active gives me a build error property does not exist on type Store<any>
This guy applies a nice solution to this problem https://www.youtube.com/watch?v=V9MmoBAezD8
in a nutshell, you have to create a shims-vuex.d.ts to solve this problem, as documented by vuex v4.0.0 release
the State type will be exported from your store/index.ts and it should be a representation of your state
import { State } from './store/index.ts';
declare module '#vue/runtime-core' {
interface ComponentCustomProperties {
$store: Store<State>;
}
}
// Vuex#4.0.0-beta.1 is missing the typing for `useStore`. See https://github.com/vuejs/vuex/issues/1736
declare module 'vuex' {
export function useStore(key?: string): Store<State>;
}

Webpack external JS file not defined when loaded in Vue.js 2 component

I need to include a function from an external JS file in a Vue.js component. I've referenced this answer to figure out how to load the external file in my webpack config. My current setup looks like this:
webpack.dev.conf.js
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
[...]
new HtmlWebpackExternalsPlugin({
externals: [{
module: 'iframeresize',
entry: 'https://[...]/iframeResizer.min.js',
global: 'iframeresize'
}]
})
index.html
<script src="https://[...]/iframeResizer.min.js"></script>
.vue component
import { iFrameResize } from 'iframeresize'
export default {
name: 'FMFrame',
mounted () {
iFrameResize()
}
}
However, I'm getting an error from vue-router now.
[vue-router] Failed to resolve async component default:
ReferenceError: iframeresize is not defined
[vue-router] uncaught error during route navigation:
ReferenceError: iframeresize is not defined
Is there a step I'm missing in loading the function from the external file? I can use the function when loaded directly from index.html, but then I get a warning that the function is undefined as my webpack config seems to be ignored.
I believe this is happening because you are using a "named" import. (e.g. with braces)
If you are going to use braces then the the named import must contain an export.
import {foo} from 'foo'
then foo.js should contain
export const foo = ...
So in your case you need to use a default import without the braces which will automatically get included in the export default statement.
Simply use the 'default' import syntax.
import foo from 'foo'
Not really all that important but just to help understanding, a default import can actually be imported with braces by using the special name default
import { default as foo } from 'foo';
Further if a module has several named exports in it you can import them all and then refer to them by property.
import * as foo from 'bar'; // has two named exports doThis and doThat
//reference the named exports later with..
foo.doThis();
foo.doThat();
One problem could be the import expression, change with:
import iFrameResize from 'iframeresize'
UPDATE: just reproduced the problem and the above import works correctly.
NOTE Also remember to instantiate the plugin HtmlWebpackExternalsPlugin after your instance of html-webpack-plugin (see the Usage section of the docs)
this is the plugin configuration that I've used (change the global option value):
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'iframeresize',
entry: 'https://<url_path>/iframeResizer.js',
global: 'iFrameResize'
}
]
}),

Using mixins with Vuejs

I'm currently learning how to develop an app with Vuejs. I have a main.js file with the code for setting up Vue.js. I created a new directory /mixins with a new file api.js. I want to use that as mixin so that every component can use a function to access my api. But I don't know how to do it.
This is my /mixins/api.js file:
export default{
callapi() {
alert('code to call an api');
},
};
This is my main.js file:
import Vue from 'vue';
import VueRouter from 'vue-router';
import VueResource from 'vue-resource';
import { configRouter } from './routeconfig';
import CallAPI from './mixins/api.js';
// Register to vue
Vue.use(VueResource);
Vue.use(VueRouter);
// Create Router
const router = new VueRouter({
history: true,
saveScrollPosition: true,
});
// Configure router
configRouter(router);
// Go!
const App = Vue.extend(
require('./components/app.vue')
);
router.start(App, '#app');
How can I include my mixin the right way now, so that every component has access to the callapi() function?
If you want to use a mixin on a specific component, rather than all components, you can do it like this:
mixin.js
export default {
methods: {
myMethod() { .. }
}
}
component.vue
import mixin from 'mixin';
export default {
mixins: [ mixin ]
}
Another thing you might consider is using a component extension design pattern i.e. creating a base component and then inheriting from that in sub components. It's a little more involved but keeps code DRY if you have many components sharing many options and perhaps inheriting the template too.
I've written about it on my blog if you're interested.
You can apply mixin globally using Vue.mixin
api.js
------
export default {
methods: {
callapi() {};
}
}
main.js
-------
import CallAPI from './mixins/api.js';
Vue.mixin(CallAPI)
As the documentation states you should use it carefully:
Use global mixins sparsely and carefully, because it affects every single Vue instance created, including third party components.

Categories

Resources