Vue props with #emit - vue.js

I am not using a prop with #emit in the correct way but I don't know how to fix it. I need to know a non-global registration way of doing this correctly (I am completely new to Vue by the way)..
Here is my html in file parent.vue:
<deleteLayoutDialog v-if"showDeleteLayoutDialog"
:layout-name="dialogNameToDelete
#confirm="removeLayout(layout-name)"
#cancel="setShowDeleteLayoutDialog(false)"/>
Here is a file child.vue where deleteLayoutDialog's prop and #emit are defined:
// html
// <script>
// import { //sth } from 'files'
// #Component({ // components })
export default class DeleteLayoutDialog extends Vue {
#Prop({required: true, type: String})
public readonly layoutName: string;
#Emit()
public confirm(layoutName: string) {
// do something
}
}
</script>
Here is my javascript (inside parent.vue) where columnLayoutName appears to have a NaN value (in chrome dev tool)
public removeLayout(columnLayoutName: string) {
if (this.columnLayout.name === columnLayoutName) {
// (this.columnLayout is defined somewhere in this code)
// do something...
}
}
How should I change my html and removeLayout so that I am using prop properly? Thank you.

You are calling removeLayout instead of passing it as an argument.
#confirm="removeLayout(layout-name)"
You are trying to do a subtraction between layout and name, which are probably undefined, so you get NaN.
Just pass the reference
#confirm="removeLayout"

Related

Storing Objects in Vue Templates

I create an object from a class in the mounted event. I need to use this object throughout my component.
I've been using a data field to store the object (its not a JSON object, its a full on instantiated class).
Where is the best place to store this object? So I can use it throughout my component?
Based on your comment, it sounds like you're looking for ways to declare non-reactive data, scoped to each component instance.
Option 1: Use an attached property
Assign this.VARNAME = VALUE
Typically done in the created() hook, but can be just about anywhere in the component context
IntelliSense in IDEs may have trouble discovering this property (TypeScript will require type assertions/declarations)
Example:
export default {
created() {
this.myNonReactiveProp = new MyClass()
}
}
Option 2: Use a data property with Object.freeze()
Object.freeze() prevents the property from being reactive, but also make its completely readonly
Can be useful for static data
IntelliSense can detect this property (as it does for all data() properties)
Example:
export default {
data() {
return {
myNonReactiveProp: Object.freeze(new MyClass())
}
}
}
You can pass class between vue components via plugin:
https://v2.vuejs.org/v2/guide/plugins.html
Install your plugin globally, you should call the class like this, for example:
class myClass {
// ...
}
Vue.prototype.$myClass = new myClass;
OR in the component only:
<script>
class test {
}
export default {
data: () => ({
instance: new test
}),
mounted() {
console.log(this.instance)
}
}
</script>

Proper typing with Vue2 + vue-property-decorator + #vue/composition-api (TypeScript)

Suppose a Vue component is written like
import { Vue, Component } from "vue-property-decorator";
// ...
#Component({
setup () {
const { something } = useComposable();
return { something }
}
})
export default class DummyComponent extends Vue {
doSomething(command: string) {
this.something // issue
}
}
Two issues here:
TS2339: Property 'something' does not exist on type 'DummyComponent'. and
this.something is not typed.
Adding an index signature ([x: string]: any;) seems like a bad workaround to satisfy the linter / tsc and setting the return type of the setup method does not solve the issue #1. Is there the right way to do it or am I mixing stuff that's not supposed to be mixed. I'm sure the awesome Vue devs thought of that?
What's the proper way to satisfy tsc?

Vuex watch not triggereing on all state changes

I am trying to watch for vuex state changes in a vue component. (I am extending a larger project and would like to react to table rows being clicked in one component by taking some of the row information and render an image based on this.)
The state change works (I can see it in the vue developer tools), but the watch is only triggered once on initial load/hot reload. I am new to vue and vuex (and pug and ts) so I guess I am missing something basic.
I found a lot of answers here stating that one should just watch the store, obviously there is more to it. I tried several variations (directly accessing the store, importing the store module and using a computed property, none of which is working, see code below. What do I need to do to make any of the watches working?
When I click the test button I created, all information is present, too.
<template lang="pug">
div
p current image {{ imageUrl }}
el-button(plain #click="onMyButtonClick") Button
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
import { Getter, Action, Mutation, State } from 'vuex-class';
import { RecordModuleState } from '../store/modules/records/types';
#Component
export default class ImageViewer extends Vue {
#State('records') private records!: RecordModuleState;
onMyButtonClick (): void {
console.log('Button clicked:', this.records.currentRow)
}
get imageUrl() {
return this.records.currentRow // also tried this.$store.state.records.currentRow
}
#Watch('this.$store.state.records', { immediate:true, deep: true })
onCurrentRowChanged(val: string, oldVal: string) {
console.log('watch1', val)
}
#Watch('records', { immediate:true, deep: true })
onCurrentRowChanged2(val: string, oldVal: string) {
console.log('watch2', val)
}
#Watch('imageUrl', { immediate:true, deep: true })
onCurrentRowChanged3(val: string, oldVal: string) {
console.log('watch3', val)
}
}
</script>
Instead of using #State('records') private records!: RecordModuleState;
you should be using #Getter records.

Resolve Vue component names on the fly

I'm searching for a way to resolve Vue (sfc) component names 'on the fly'. Basically I'm getting kind of component names from a backend and need to translate them into real component names on the frontend. Something like this:
<text/> => /components/TextElement.vue
or
<component v-bind:is="text"></component> => => /components/TextElement.vue
The idea is providing a global function that maps the backend names to the frontend names. I don't want to do this in every place where those components are used, so maybe there is a kind of hook to change the names dynamically?
Thanks a lot,
Boris
You could create a wrapper <component> that maps the component names and globally registers the specified component on the fly.
Create a component with a prop (e.g., named "xIs") for the <component>'s is property:
export default {
props: {
xIs: {
type: String,
required: true
}
},
};
Add a template that wraps <component>, and a data property (e.g., named "translatedName") that contains the mapped component name (from backend to front-end name):
export default {
data() {
return {
translatedName: ''
}
}
};
Add a watcher on the prop that will register a component by the name indicated in the prop value:
import componentNames from './component-names.json';
export default {
watch: {
xIs: {
immediate: true, // run handler immediately on current `xIs`
async handler(value) {
try {
const name = componentNames[value]; // 1. lookup real component name
if (name) {
Vue.component(name, () => import(`#/components/${name}`)); // 2. register component
this.translatedName = name; // 3. set name for
}
} catch (e) {
console.warn(`cannot resolve component name: ${value}`, e);
}
}
}
}
}

Component without template

I have a bit of code that makes an api call to a server and returns some JSON.
It did exist as a method in my component but as it is getting a bit long I want to extract it to it's own file
In vuejs what is the best practice here.
should it be a component without a template? How would this work?
will I just create an es6 module?
I would suggest using a mixin here.
In a file like myCoolMixin.js define your mixin...
export default {
methods: {
myAwesomeMethod() {
//do something cool...
}
}
}
You can define anything in a mixin just like a component. e.g. data object, computed or watched properties, etc. Then you simply include the mixin in your component.
import myCoolMixin from '../path/to/myCoolMixin.js'
export default {
mixins: [myCoolMixin],
data: function() {
return: {
//...
}
},
mounted: function() {
this.myAwesomeMethod(); // Use your method like this!
}
}
More on Mixins here: https://v2.vuejs.org/v2/guide/mixins.html
Mixins work, or you could create a plugin. Here's the docs example:
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
Vue Plugins