Why does my vue code not work as expected? - javascript

there are two components in div,When the two components were rendered together, I clicked the button to switch properly, but in the case of rendering only one component, the switch becomes abnormal.
this is my code
Base.vue
<template>
<div :id="id">{{msg}}</div>
</template>
<script lang='ts'>
import { Component, Prop } from "vue-property-decorator";
import Vue from "vue";
#Component
export default class Base extends Vue {
id!: string;
msg = "this is Base";
}
</script>
child.vue(no template)
<script lang='ts'>
import Base from "#/components/Base.vue";
import { Prop, Component } from "vue-property-decorator";
#Component
export default class extends Base {
#Prop({ default: "helloWorld" })
childId!: string;
constructor() {
super();
this.id = this.childId;
this.msg = "this is Child " + this.childId;
}
}
</script>
App.vue(display these components)
<template>
<div id="app">
<Child v-show="!show" childId="child1" style="color:#f00;"/>
<button #click="click">change</button>
<Child v-show="show" childId="child2" style="color:#f0f;"/>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Child from "#/components/Child.vue";
import Component from "vue-class-component";
#Component({
components:{
Child,
}
})
export default class App extends Vue {
show= false;
click() {
this.show = !this.show;
}
}
</script>
and click the button the result is
These results are expected. But if all the v-show in the app. vue above are changed to v-if, the result is confusing
then click the button the result is
In our expectation it should display child2 here. So why does this happen?

Your first click creates the the show-property which didn't exist because you didn't create your data() properly.
I'll not speculate exactly in the reasons why, but I assume there might be some funny boolean casts, and the property might not be reactive since it's not in data. Either way, just create it and it'll work as you intended:
export default class App extends Vue {
data(){
return {
show: false
}
},
click() {
this.show = !this.show;
}
}

Thanks!!
I solved this problem when I added different keys to the two Child components
<Child v-if="!show" childId="child1" key="hello1" style="color:#f00;" />
<Child v-if="show" childId="child2" key="hello2" style="color:#f0f;" />
I think the reason is Vue's diff algorithm, Vue considers these two components to be the same component

Because when you use v-if, it will use the single same Child component. The this.msg will only change once in the constructor. The msg will not change when the childId props changed, so you need the Watch. When the childId changed, then to update the msg
Child.vue
<script lang='ts'>
import Base from "#/components/Base.vue";
import { Prop, Component, Watch } from "vue-property-decorator";
#Component
export default class extends Base {
#Prop({ default: "helloWorld" })
childId!: string;
#Watch('childId')
onChildIdChanged(val: any) {this.msg = "this is Child " + val}
constructor() {
super();
this.id = this.childId;
this.msg = "this is Child " + this.childId;
}
}
</script>

Related

Vue reusable modal wrapper

I am wanting to have a modal wrapper that I can inject components into, so the modal is responsible for things like being closed, but the injected component is repsonsible what is show, and what is done with the data. So far I have this solution,
const Modal = namespace("Modal");
#Component
export default class AppModal extends Vue {
public component: any = null;
#Modal.State
public modalVisible!: boolean;
#Modal.State
public modalComponent!: string;
get injectedComponent() {
return this.modalComponent;
}
#Modal.Mutation
public hideModal!: () => void
#Watch('injectedComponent')
onModalComponent(componentName: string) {
if(!componentName) return;
debugger;
Vue.component(componentName, () => import(`./components/${componentName}`))
this.component = componentName;
}
The showModal method in the store, makes the modalVisible and takes a componentName, we listen for this change and import the component, and use a dynamic component to inject it into the modal.
<template>
<v-dialog v-model="modalVisible" class="muc-modal" max-width="350" persistent>
<v-card>
<component :is="modalComponent"/>
</v-card>
</v-dialog>
Whatever componentName I send to the watcher I get the following error,
Unknown custom element:
It's like it can't resolve the component I wanting to send into my AppModal. Am I doing something incorrectly?
To get to work, you must import and register all the components that you will be passing in the prop :is="...". So, you modalComponent must have imports and registrations for all the components that will be called by
For example, if you will be using the components Comp1, and Comp2 alternatively, you should have something like the following in your modalComponent
import Comp1 from '<path-to-Comp1>
import Comp2 from '<path-to-Comp2>
#Component({
components: {
Comp1,
Comp2,
}
})
export default class AppModal extends Vue {
...
...
...
<You fill in modalComponent with Comp1 or Comp2>
...
...
...
}
And in your template you can have
<component :is="modalComponent"/>
Good luck.

Why does this.renderChart not exist on CombinedVueInstance? [duplicate]

While rewriting my VueJs project in typescript, I came across a TypeScript error.
This is a part of the component that has a custom v-model.
An input field in the html has a ref called 'plate' and I want to access the value of that. The #input on that field calls the update method written below.
Typescript is complaining that value does not exist on plate.
#Prop() value: any;
update() {
this.$emit('input',
plate: this.$refs.plate.value
});
}
template:
<template>
<div>
<div class="form-group">
<label for="inputPlate" class="col-sm-2 control-label">Plate</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPlate" ref="plate" :value="value.plate" #input="update">
</div>
</div>
</div>
</template>
You can do this:
class YourComponent extends Vue {
$refs!: {
checkboxElement: HTMLFormElement
}
someMethod () {
this.$refs.checkboxElement.checked
}
}
From this issue: https://github.com/vuejs/vue-class-component/issues/94
Edit - 2021-03 (Composition API)
Updating this answer because Vue 3 (or the composition API plugin if you're using Vue 2) has some new functions.
<template>
<div ref="root">This is a root element</div>
</template>
<script lang="ts">
import { ref, onMounted, defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
})
</script>
Edit - 2020-04:
The vue-property-decorator library provides #Ref which I recommend instead of my original answer.
import { Vue, Component, Ref } from 'vue-property-decorator'
import AnotherComponent from '#/path/to/another-component.vue'
#Component
export default class YourComponent extends Vue {
#Ref() readonly anotherComponent!: AnotherComponent
#Ref('aButton') readonly button!: HTMLButtonElement
}
Original Answer
None of the above answers worked for what I was trying to do. Adding the following $refs property wound up fixing it and seemed to restore the expected properties. I found the solution linked on this github post.
class YourComponent extends Vue {
$refs!: {
vue: Vue,
element: HTMLInputElement,
vues: Vue[],
elements: HTMLInputElement[]
}
someMethod () {
this.$refs.<element>.<attribute>
}
}
son.vue
const Son = Vue.extend({
components: {},
props: {},
methods: {
help(){}
}
...
})
export type SonRef = InstanceType<typeof Son>;
export default Son;
parent.vue
<son ref="son" />
computed: {
son(): SonRef {
return this.$refs.son as SonRef;
}
}
//use
this.son.help();
This worked for me: use
(this.$refs.<refField> as any).value or (this.$refs.['refField'] as any).value
Avoid using bracket < > to typecast because it will conflict with JSX.
Try this instead
update() {
const plateElement = this.$refs.plate as HTMLInputElement
this.$emit('input', { plate: plateElement.value });
}
as a note that I always keep remembering
Typescript is just Javascript with strong typing capability to ensure type safety. So (usually) it doesn't predict the type of X (var, param, etc) neither automatically typecasted any operation.
Also, another purpose of the typescript is to make JS code became clearer/readable, so always define the type whenever is possible.
Maybe it will be useful to someone. It looks more beautiful and remains type support.
HTML:
<input ref="inputComment" v-model="inputComment">
TS:
const inputValue = ((this.$refs.inputComment as Vue).$el as HTMLInputElement).value;
In case of custom component method call,
we can typecast that component name, so it's easy to refer to that method.
e.g.
(this.$refs.annotator as AnnotatorComponent).saveObjects();
where AnnotatorComponent is class based vue component as below.
#Component
export default class AnnotatorComponent extends Vue {
public saveObjects() {
// Custom code
}
}
With Vue 3 and the Options API, this is what worked for me:
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
methods: {
someAction() {
(this.$refs.foo as HTMLInputElement).value = 'abc';
},
},
});
</script>
The autocomplete doesn't bring the foo property from $refs because it's defined in the template, and apparently there's no information inferred from it.
However, once you force the casting of .foo to the HTML element type, everything works from there on, so you can access any element property (like .value, in the example above).
Make sure to wrap your exports with Vue.extend() if you are converting your existing vue project from js to ts and want to keep the old format.
Before:
<script lang="ts">
export default {
mounted() {
let element = this.$refs.graph;
...
After:
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
mounted() {
let element = this.$refs.graph;
...
I found a way to make it work but it is ugly in my opinion.
Feel free to give other/better suggestions.
update() {
this.$emit('input', {
plate: (<any>this.$refs.plate).value,
});
}
I spent a LONG time trying to find an answer to this using Vue 3, TypeScript with class components and (as it happens, although not relevant to this) TipTap. Found the answer from bestRenekton above which finally solved it, but it needed tweaking. I'm pretty sure this is TypeScript specific.
My child component has this at the start:
export default class WhealEditor extends Vue {
It includes this method (the one I want to call from the parent):
doThis(what: string) {
console.log('Called with ' + what)
}
And this right at the end:
export type EditorRef = InstanceType<typeof WhealEditor>
</script>
So this announces to any consumer of the child component that it can access it using the variable EditorRef. The parent component includes the child component in the template:
<WhealEditor ref="refEditor" />
The parent component then imports ref, and the child component and the exposed object:
import { ref } from 'vue'
import WhealEditor, { EditorRef } from './components/WhealEditor.vue'
I then have a method to get this object:
getEditor(): EditorRef {
// gets a reference to the child component
return this.$refs.refEditor as EditorRef
}
Finally, I can handle events - for example:
processButton(msg: string) {
// runs method in child component
this.getEditor().doThis(msg)
Like everything else to do with client script, it's so much harder than I expected!

Vue DOM element scrollTop is inaccessible [duplicate]

While rewriting my VueJs project in typescript, I came across a TypeScript error.
This is a part of the component that has a custom v-model.
An input field in the html has a ref called 'plate' and I want to access the value of that. The #input on that field calls the update method written below.
Typescript is complaining that value does not exist on plate.
#Prop() value: any;
update() {
this.$emit('input',
plate: this.$refs.plate.value
});
}
template:
<template>
<div>
<div class="form-group">
<label for="inputPlate" class="col-sm-2 control-label">Plate</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPlate" ref="plate" :value="value.plate" #input="update">
</div>
</div>
</div>
</template>
You can do this:
class YourComponent extends Vue {
$refs!: {
checkboxElement: HTMLFormElement
}
someMethod () {
this.$refs.checkboxElement.checked
}
}
From this issue: https://github.com/vuejs/vue-class-component/issues/94
Edit - 2021-03 (Composition API)
Updating this answer because Vue 3 (or the composition API plugin if you're using Vue 2) has some new functions.
<template>
<div ref="root">This is a root element</div>
</template>
<script lang="ts">
import { ref, onMounted, defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
})
</script>
Edit - 2020-04:
The vue-property-decorator library provides #Ref which I recommend instead of my original answer.
import { Vue, Component, Ref } from 'vue-property-decorator'
import AnotherComponent from '#/path/to/another-component.vue'
#Component
export default class YourComponent extends Vue {
#Ref() readonly anotherComponent!: AnotherComponent
#Ref('aButton') readonly button!: HTMLButtonElement
}
Original Answer
None of the above answers worked for what I was trying to do. Adding the following $refs property wound up fixing it and seemed to restore the expected properties. I found the solution linked on this github post.
class YourComponent extends Vue {
$refs!: {
vue: Vue,
element: HTMLInputElement,
vues: Vue[],
elements: HTMLInputElement[]
}
someMethod () {
this.$refs.<element>.<attribute>
}
}
son.vue
const Son = Vue.extend({
components: {},
props: {},
methods: {
help(){}
}
...
})
export type SonRef = InstanceType<typeof Son>;
export default Son;
parent.vue
<son ref="son" />
computed: {
son(): SonRef {
return this.$refs.son as SonRef;
}
}
//use
this.son.help();
This worked for me: use
(this.$refs.<refField> as any).value or (this.$refs.['refField'] as any).value
Avoid using bracket < > to typecast because it will conflict with JSX.
Try this instead
update() {
const plateElement = this.$refs.plate as HTMLInputElement
this.$emit('input', { plate: plateElement.value });
}
as a note that I always keep remembering
Typescript is just Javascript with strong typing capability to ensure type safety. So (usually) it doesn't predict the type of X (var, param, etc) neither automatically typecasted any operation.
Also, another purpose of the typescript is to make JS code became clearer/readable, so always define the type whenever is possible.
Maybe it will be useful to someone. It looks more beautiful and remains type support.
HTML:
<input ref="inputComment" v-model="inputComment">
TS:
const inputValue = ((this.$refs.inputComment as Vue).$el as HTMLInputElement).value;
In case of custom component method call,
we can typecast that component name, so it's easy to refer to that method.
e.g.
(this.$refs.annotator as AnnotatorComponent).saveObjects();
where AnnotatorComponent is class based vue component as below.
#Component
export default class AnnotatorComponent extends Vue {
public saveObjects() {
// Custom code
}
}
With Vue 3 and the Options API, this is what worked for me:
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
methods: {
someAction() {
(this.$refs.foo as HTMLInputElement).value = 'abc';
},
},
});
</script>
The autocomplete doesn't bring the foo property from $refs because it's defined in the template, and apparently there's no information inferred from it.
However, once you force the casting of .foo to the HTML element type, everything works from there on, so you can access any element property (like .value, in the example above).
Make sure to wrap your exports with Vue.extend() if you are converting your existing vue project from js to ts and want to keep the old format.
Before:
<script lang="ts">
export default {
mounted() {
let element = this.$refs.graph;
...
After:
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
mounted() {
let element = this.$refs.graph;
...
I found a way to make it work but it is ugly in my opinion.
Feel free to give other/better suggestions.
update() {
this.$emit('input', {
plate: (<any>this.$refs.plate).value,
});
}
I spent a LONG time trying to find an answer to this using Vue 3, TypeScript with class components and (as it happens, although not relevant to this) TipTap. Found the answer from bestRenekton above which finally solved it, but it needed tweaking. I'm pretty sure this is TypeScript specific.
My child component has this at the start:
export default class WhealEditor extends Vue {
It includes this method (the one I want to call from the parent):
doThis(what: string) {
console.log('Called with ' + what)
}
And this right at the end:
export type EditorRef = InstanceType<typeof WhealEditor>
</script>
So this announces to any consumer of the child component that it can access it using the variable EditorRef. The parent component includes the child component in the template:
<WhealEditor ref="refEditor" />
The parent component then imports ref, and the child component and the exposed object:
import { ref } from 'vue'
import WhealEditor, { EditorRef } from './components/WhealEditor.vue'
I then have a method to get this object:
getEditor(): EditorRef {
// gets a reference to the child component
return this.$refs.refEditor as EditorRef
}
Finally, I can handle events - for example:
processButton(msg: string) {
// runs method in child component
this.getEditor().doThis(msg)
Like everything else to do with client script, it's so much harder than I expected!

"Expected Object, got Promise." error when promise passed as a prop

I just try to pass a Promise as a Prop to a child component which expect a Promise but I have an error that tells the expected type is Object.
I use TypeScript, vue-property-decorator and vue-class-component for my project.
My code looks like this:
<template>
<child-component :listPromise="listAsPromise"/>
</template>
<script lang="ts">
import { Component, Vue, Prop, Inject } from 'vue-property-decorator'
import { EditableReferentielModel } from '#/api/model/referentiel'
import ReferentielResource from '#/api/resources/referentiel'
import { AxiosResponseExt } from '#/api/resources/index'
export default class ParentComponent extends Vue {
#Inject()
referentielResource: ReferentielResource
listAsPromise: Promise<EditableReferentielModel[] & AxiosResponseExt> = this.referentielResource.getStadeDeveloppement()
}
</script>
<script lang="ts">
import { Component, Vue, Prop, Inject } from 'vue-property-decorator'
import ReferentielResource from '#/api/resources/referentiel'
import { EditableReferentielModel } from '#/api/model/referentiel'
import { AxiosResponseExt } from '#/api/resources/index'
#Component
export default class ChildComponent extends Vue {
#Inject()
referentielResource: ReferentielResource
#Prop({ default: null })
value: EditableReferentielModel | null
#Prop({ default: null })
listPromise: Promise<EditableReferentielModel[] & AxiosResponseExt>
referentiel: EditableReferentielModel[] = []
selected: EditableReferentielModel | null = this.value
async mounted () {
this.referentiel = await this.listPromise
}
}
</script>
With this code, I still have this error: [Vue warn]: Invalid prop: type check failed for prop "listPromise". Expected Object, got Promise. even if the listPromise type is set to any.
What is wrong ?

Angular 2 - Get passed object to component via inputs

On my parent page I have a link here:
<a (click)="showPermissionsRates(5757);">Link</a>
The function sets it:
showPermissionsRates(item) {
this.currentEventPoolId = item;
}
With a child component on the parent page here:
<app-event-pools-permissions-rates [eventPoolId]="currentEventPoolId "></app-event-pools-permissions-rates>
And then in my child component TS file I use:
inputs: ['eventPoolId']
But how do I get that value of '5757' in the child component? Such as using alert?
You should be able to just use #Input() on the child property.
I've put this together showing a VERY basic example, but without more to go on regarding your issues, it's hard to know what you need:
https://plnkr.co/edit/y9clOla1WrPFmhMJoz7o?p=preview
The gist is to use #Input() to mark your inputs in the child component, and map those in the template of the parent.
import {Component} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import { ChildComponent } from 'child.component.ts';
#Component({
selector: 'my-app',
template: `
<div>
<button (click)="changeProperty('ABC 123')">Click Me!</button>
<child-component [childProperty]="parentProperty"></child-component>
</div>
`,
})
export class App {
public parentProperty: string = "parentProp";
public changeProperty(newProperty: string) : void {
this.parentProperty = newProperty;
}
}
Then, in the child:
import {Component, Input} from '#angular/core'
#Component({
selector: 'child-component',
template: `
<div>Hello World: {{ childProperty }}</div>
`,
})
export class ChildComponent {
#Input()
childProperty:string;
constructor() {
this.childProperty = 'childProp'
}
}
I think you are setting value to at input variable in a click event, then you have to listen for it in the child component constructor using ngonchanges
ngOnChanges(changes: SimpleChanges) {
if(changes['eventpoolid'] && changes['eventpoolid'].currentValue) {
// you get updated value here
}
}

Categories