In vue3, I try to add attributes like $attrsWithoutIdClassStyle and $attrsIdClassStyleOnly to the globalProperties which the values are based on the $attrs of current vue-component
It can be achieved by the global mixins
import _ from 'lodash';
import { createApp } from 'vue';
import App from '~/App.vue';
createApp(App)
mixin({
computed: {
$attrsWithoutIdClassStyle() {
return _.omit(this.$attrs, ['id', 'class', 'style']);
},
$attrsIdClassStyleOnly() {
return _.pick(this.$attrs, ['id', 'class', 'style']);
},
}
})
But since the mixin is not recommended in vue3 any more, also the code is ugly.
So, how can I add the custom attributes to the global properties via app-config-globalproperties which is a dynamic value based on the current vm?
As the documentation suggests, the proper Vue 3 replacement is to use a composable function and import where needed.
composables/myAttrs.js
import { useAttrs } from 'vue';
import _ from 'lodash';
export function useMyAttrs() {
const attrs = useAttrs();
const attrsWithoutIdClassStyle = _.omit(attrs, ['id', 'class', 'style']);
const attrsIdClassStyleOnly = _.pick(attrs, ['id', 'class', 'style']);
return {
attrsWithoutIdClassStyle,
attrsIdClassStyleOnly
};
}
in your components,
import { useMyAttrs } from '#/composables/myAttrs';
const { attrsWithoutIdClassStyle, attrsIdClassStyleOnly } = useMyAttrs();
Related
I have a multiple Svelte stores that looks like this:
import { readable } from "svelte/store";
export const NatureStore = readable([
{
name: "nature",
description:
"Nature is what makes us all so we need to beautify it. This represent what we all need to survive in a civilization.",
images: {
src: [
"...",
],
description: [
"...",
],
},
},
]);
All my stores have the same organization.
Now my main problem is that I need to subscribe to a specific store depending on a variable when the component is loaded.
Like for example if my variable is var = 'nature' I want to subscribe to nature. I could do it with ifs and elses, but that won't be efficient at all. So I wonder if there's a way to do it in a better way.
For now I only subscribe to one store and it looks like that:
<script>
import { NatureStore as nature } from "../../stores/nature";
import { BlooperStore } from "../../stores/bloopers";
import { NightStore } from "../../stores/night";
import { PortraitStore } from "../../stores/portrait";
import { PurpleStore } from "../../stores/purple";
import { UrbanStore } from "../../stores/urban";
import { UrbexStore } from "../../stores/urbex";
import { onDestroy } from "svelte";
//parameter passed with the router
export let params;
let category = params.category;
let pictures = [];
const unsubscribe = nature.subscribe((data) => {
pictures = data;
});
onDestroy(() => unsubscribe());
</script>
But i want something like this :
<script>
import { NatureStore as nature } from "../../stores/nature";
import { BlooperStore } from "../../stores/bloopers";
import { NightStore } from "../../stores/night";
import { PortraitStore } from "../../stores/portrait";
import { PurpleStore } from "../../stores/purple";
import { UrbanStore } from "../../stores/urban";
import { UrbexStore } from "../../stores/urbex";
import { onDestroy } from "svelte";
//parameter passed with the router
export let params;
let category = params.category;
let pictures = [];
//here setting the subscribe to the category variable
const unsubscribe = category.subscribe((data) => {
pictures = data;
});
onDestroy(() => unsubscribe());
</script>
If my explanations are not clear leave a comment I'll answer as quick as possible.
Thanks for your help.
You can create a mapping from category to store and just use that to find the correct one. The mapping could be extracted from the stores themselves or has to be created manually. As the store contents are an array, not an object, the former approach might not work.
One should not manually subscribe in Svelte files, just use $-syntax and reactive statements. That way you can't forget to unsubscribe and that happens automatically.
E.g.
<script>
import { NatureStore } from "../../stores/nature";
import { BlooperStore } from "../../stores/bloopers";
import { NightStore } from "../../stores/night";
import { PortraitStore } from "../../stores/portrait";
import { PurpleStore } from "../../stores/purple";
import { UrbanStore } from "../../stores/urban";
import { UrbexStore } from "../../stores/urbex";
export let params;
$: category = params.category;
const mapping = {
nature: NatureStore,
bloopers: BlooperStore,
night: NightStore,
portrait: PortraitStore,
purple: PurpleStore,
urban: UrbanStore,
urbex: UrbexStore,
}
$: store = mapping[category];
$: pictures = $store;
</script>
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.
It seems that Vue Meta has been upgraded to handle Vue.js 3 with a new npm package called vue-3-meta
Before Vue.js 3, it was easy to use vue-meta by adding it to the Vue instance:
import Vue from 'vue'
import VueMeta from 'vue-meta'
Vue.use(VueMeta, {
// optional pluginOptions
refreshOnceOnNavigation: true
})
However in Vue.js 3, there is no Vue instance; and instead you create the app by running createApp like such:
const app = createApp(App);
const router = createVueRouter();
app.use(router);
// need to make app use Vue-Meta here
I cannot find any documentation for vue-3-meta. import VueMeta from 'vue-meta' no longer works.
How do I import the vue-3-meta plugin properly and use it with app like in prior versions?
Disclaimer: vue-meta v3 is still in alpha!
This was the minimal implementation I needed to get started:
Update vue-meta to v3 (in package.json)
- "vue-meta": "^2.4.0",
+ "vue-meta": "^3.0.0-alpha.7",
...or with yarn:
yarn add vue-meta#alpha
Add metaManager to Vue app
import { createMetaManager } from 'vue-meta'
const app = createApp(App)
.use(router)
.use(store)
.use(createMetaManager()) // add this line
await router.isReady()
app.mount('#app')
Add <metainfo> to App.vue <template> (this is also where I set a "title template")
<template>
<metainfo>
<template v-slot:title="{ content }">{{ content ? `${content} | SITE_NAME` : `SITE_NAME` }}</template>
</metainfo>
<header />
<router-view />
<footer />
</template>
Set default meta in App.vue <script>
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
}
or with vue-class-component:
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class App extends Vue {
meta = setup(() => useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
Override meta in each component
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({ title: 'Some Page' })
}
}
or with vue-class-component:
import { computed } from '#vue/runtime-core'
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class SomePage extends Vue {
meta = setup(() => useMeta(
computed(() => ({ title: this.something?.field ?? 'Default' })))
)
}
See also:
"Quick Usage" (vue-meta next branch)
Vue Router Example (vue-meta next branch)
In addition to the previous answers, I also needed to add a transpileDependency in my vue.config.js, as I was using vue-cli:
module.exports = {
transpileDependencies: ['vue-meta']
}
Else, I would get the error:
error in ./node_modules/vue-meta/dist/vue-meta.esm-browser.min.js
Module parse failed: Unexpected token (8:7170)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
Thanks to this thread for pointing me to this: https://stackoverflow.com/a/65844988/3433137
metaManager is a MetaManager instance created from createMetaManager() of vue-meta.
Based on the Vue 3 + Vue Router example for vue-meta, here's an example usage:
import { createApp } from 'vue'
import { createMetaManager, defaultConfig, resolveOption, useMeta } from 'vue-meta'
const decisionMaker5000000 = resolveOption((prevValue, context) => {
const { uid = 0 } = context.vm || {}
if (!prevValue || prevValue < uid) {
return uid
}
})
const metaManager = createMetaManager({
...defaultConfig,
esi: {
group: true,
namespaced: true,
attributes: ['src', 'test', 'text']
}
}, decisionMaker5000000)
useMeta(
{
og: {
something: 'test'
}
},
metaManager
)
createApp(App).use(metaManager).mount('#app')
I'm trying to extract a function for use across multiple components, but "this" is undefined and I'm unsure of the best practice approach of how to attach the scope so my function knows what "this" is. Can I just pass it as an argument?
Component:-
import goToEvent from "#/common";
export default {
name: "update",
methods: {
goToEvent
common function:-
let goToEvent = (event, upcoming=false) => {
this.$store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
export default goToEvent
When I call goToEvent in my component, I get TypeError: Cannot read property '$store' of undefined. How do I avoid this?
In this situation I recommend to define eventable as a mixin :
const eventable= {
methods: {
goToEvent(event, upcoming=false) {
this.$store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
}
}
export default eventable;
in your vue file :
import eventable from "#/eventable";
export default {
name: "update",
mixins:[eventable],
....
second solution :
export an object with the function as nested method then import it and spread it inside the methods option :
export default {
goToEvent(event, upcoming=false){
this.$store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
}
then :
import goToEvent from "#/common";
export default {
name: "update",
methods: {
...goToEvent,
otherMethod(){},
}
//....
}
You're tagged with Typescript, so you need to tell TS that this actually has a value (note, I do not know VueJS, am using the generic Event types here, there is likely a more valid and correct type!)
First option, manually tell it what there is -
let goToEvent = (this:Event, event, upcoming=false) => {
Other option - tell it what type it is -
let goToEvent: EventHandler = (event, upcoming=false) => {
Of the two I personally prefer the second style for readability.
There are numerous ways to achieve this, here are some that I like to use in my projects:
Method 1: Mixins
Mixins are great for sharing a bunch of methods across components and also easy to implement, although one big con is that you will not be able to import specific methods that you need. Within the mixin, this follows the rules as in components.
File: #/mixins/eventable
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([])
goToEvent (event, upcoming = false) {
store.dispatch({
type: 'setEventsDay',
day: event.start_date
})
}
}
}
Usage in component:
import eventable from '#/mixins/eventable'
export default {
name: 'ComponentName',
mixins: [eventable],
methods: {
componentMethod () {
this.goToEvent()
}
}
...
Method 2: Static JavaScript files
In some cases, you might have a collection of helper functions kept in a file and want the ability to import as you need.
In your case, you seem to be using a store actions (assumed from the dispatch), hence I'll be including importing and using the store within the static JS file.
File: #/static/js/eventable.js
import store from 'path_to_store_file'
const goToEvent = () => {
store.dispatch('actionName', payload)
}
export default {
goToEvent
}
Note:
Although this is not entirely necessary, but only by declaring the imported function as a method within the component will it be bound to the component instance. This will allow you to access the function in the HTML portion.
Usage in component:
import { goToEvent } from '#/static/js/eventable.js'
export default {
name: 'ComponentName',
methods: {
// Read note before this code block
goToEvent,
componentMethod () {
// When declared as a method
this.goToEvent()
// When not declared, it can still be accessed in the js portion like this
goToEvent()
}
}
...
I want to empty the database before my acceptance test cases.
In my aircraft.controller.acceptance.ts I have
import { givenEmptyDatabase } from './helpers/database.helpers';
...
before(givenEmptyDatabase);
...
describe( ... ) { ... }
in database.helpers.ts I try to act according to the LoopBack 4 documentation
import {AircraftRepository} from '../../src/repositories';
import {testdb} from '../fixtures/datasources/testdb.datasource';
export async function givenEmptyDatabase() {
await new AircraftRepository(testdb).deleteAll();
}
but it's never described what the testdb.datasource.ts should look like. I have tried to make it look similar to my regular db.datasource.ts, but I'm not sure what to export..
import {inject} from '#loopback/core';
import {juggler, AnyObject} from '#loopback/repository';
const config = require('./db.datasource.json');
export class DbDataSource extends juggler.DataSource {
static dataSourceName = 'db';
constructor(
#inject('datasources.config.db', {optional: true})
dsConfig: AnyObject = config
) {
super(dsConfig);
}
}
Got an answer from the #loopback/repository. testdb.datasource.ts should look like this:
import { juggler } from '#loopback/repository';
export const testdb: juggler.DataSource = new juggler.DataSource({
name: 'db',
connector: 'memory'
});