How to get div in nested child component during mounted - Vue.js - vue.js

I have problem whit scrolling to the element while opening the page. It's like scrolling to an anchor. I'm sending div id via props to the nested child component. On mounted I call a method scrollToSection where I have logic with scrolling.
props: {
section: String
},
mounted() {
this.scrollToSection();
},
methods: {
scrollToSection () {
if (this.section != null) {
const options = {
force: true,
easing: 'ease-in',
};
VueScrollTo.scrollTo(`#${this.section}`, 100, options);
}
}
}
In that point child component dosen't render the DOM and document.getElementById(this.section) return null. But in parent component I have an access to that div.
Do you have any idea how to resolve that issue? Method nextTick dosen't work.

Had the same problem, where I needed to access an image in a nested component, I had to wait till the image is loaded within the nested component, by adding a #load event listener that emit a loaded event to the parent, so I knew it was ready and perform my actions.
If nextTick isn't working, you can do the same, just emit a ready event on the mounted hook of the child component and when it's ready, perform the scroll.
//Child component
mounted(){
this.$emit('ready');
}
_
//Parent component
...
<child-component #ready="scrollToSection()" />
...
Or in alternative, if you have access to the div you want to scroll to, you can use pure javascript
MyDiv.scrollIntoView({ block: "start", behavior: 'smooth' });

Related

Trying to find the height of a div in Vue.js, I'm accessing it through refs gives error

I am trying to find the height of div in Vuejs,
let height = this.$refs.infoBox.clientHeight()+'px';
it gives me an error saying that I cannot find the client height of null / undefined.
If your div have a reference attach to it like: <div ref="infoBox" /> you can access the height of that div with this.$refs.infoBox.offsetHeight.
NOTE: You can access the height of the div only after the component has been mounted. This won't work:
{
// ...
created() {
// WARNING
// Will trigger an error because the DOM is not mounted at this point.
console.log(this.$refs.infoBox.offsetHeight);
},
// ...
}
Adding onto #geauser's example, if you would like to access a reference after the component is mounted use the mounted hook:
{
// ...
mounted() {
// DOM *is* mounted at this point.
console.log(this.$refs.infoBox.offsetHeight);
},
// ...
}
Vue lifecycle hooks.

Execute method of the embeded component with Vue js

I have a vuejs component("Main") with a dialog and I use a subcomponent("SpeechToText") into it.
When I´m going to open the dialog, I need to check if "speechInititalized" of the "SpeechToText " is true.
if yes, I´d like to call a "reset method" to stop the microphone and the user can restart as if were the first time.
How could I do that?
Here is a snippet of the code.
//Main component
<template>
<div>
<v-dialog v-model="dialogSpeech" hide-overlay persistent width="700">
<SpeechToText></SpeechToText>
<v-btn color="info" #click="closeDialogSpeech">Fechar</v-btn>
</v-dialog>
</div>
</template>
data:()=>({
speechInititalized:false,
}),
methods:{
closeDialogSpeech(){
this.dialogSpeech = false;
}
openDialogSpeech(){
//I´d like to call reset method of the SpeechToText component if it was initialized
if (speechInititalized){ //from SpeechToText data
reset(); //from SpeechToText data
}
}
}
//SpeechToText
data:()=>({
speechInititalized:false,
})
mounted(){
this.initialize();
}
methods{
initialize(){
//speech api is initialized
speechInititalized=true;
};
reset(){
//speech api is stopped
//stop microphone
//clear data
};
}
Don't check if speech was initialized in parent component. That's child component's responsibility. All you do in parent component is emit/announce the dialogue has opened.
Child component reacts to this announcement.
Proof of concept:
// parent
<template>
...
<speech-to-text :is-dialogue-open="isDialogueOpen" />
...
</template>
<script>
export default {
...
data: () => ({
isDialogueOpen: false // set this to true/false when dialogue is opened/closed
}),
...
}
// child:
export default {
props: {
isDialogueOpen: {
type: boolean,
default: false
}
},
...,
watch: {
isDialgueOpen(value) {
// reset if dialogue has just opened and speech has previously been initialized
if (value && this.speechInitialized) {
this.reset();
}
}
},
...
}
Another, more flexible and cleaner approach, preferable when the relation between parent and child is not as direct, or even dynamic and, generally, preferable in larger scale applications, is to use an eventBus (basically a singleton shared across components for emitting/listening to events).
Emit an event on the bus in any corner of the application and have as many listeners reacting to that event in as many other components in the app, regardless of their relation to the original emitter component.
Here's a neat example explaining the concept in more detail.
If you're using typescript, you might want to give vue-bus-ts a try.
This approach is similar to the previous one (emit an event when dialogue is opened and react to it in SpeechToText component), except both parent and child are now cleaner (none of them needs the isDialogueOpen prop, and you also get rid of the watch - whenever possible, avoid watch in Vue as it's more expensive than most alternatives).
Since the event listener is inside SpeechToText, you can check if speech has already been initialized.
Another benefit of this approach is that you don't have to emit/change anything when dialogue closes (unless you want to react to that event as well).

How to watch child properties changes from parent component

I am using a date picker component library and i want to watch when a property of that component changes.
I have tried this:
watch: {
'$refs.picker.popupVisible': {
handler (new_value) {
console.log('executed')
},
deep: true
}
}
Also this:
computed: {
picker () {
console.log(this.$refs.picker.popupVisible)
return this.$refs.picker.popupVisible
}
}
I know that the solution will be a vue.js hack because this is not the right way.If i had access to child component i would emit en event to parent but unfortunately i don't have.
I had a similar problem using a library which had some limitations.
Unfortunately, your watcher will not work.You have to use the function watcher to make this to work.And you have to use it inside the mounted hook.
mounted() {
this.$watch(
"$refs.picker.popupVisible",
(new_value, old_value) => {
//execute your code here
}
);
}
I also have an example. Please take a look here
What you can do is create a data object in parent component and include the date field in that data object and pass that data object to child component as props
<child :dateObj="dateObj"></child>
data: {
dateObj: {
date: ""
}
}
And in child component you can use the date field of that dateObj props. This is possible because Vue doesn't watch the property of Objects passed as props and we can modify them without Vue complaining in console.
Thus the changed date field is reflected in parent as well.

Can't copy props to model data and render it in Vue 2

I'm having this problem that looks a lot like a bug to me and I can't figure out how to solve it.
I created a generic list component and I tell it what child component it should insert in each item and what are the data it should pass to the child component. I'm passing everything as props along with the list (array) itself.
The problem is that I can't mutate the list props. So I try to copy it to model attribute. Otherwise I get this error:
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders.....
And I can't just make it work in any of the lifecycle events. When I save the file and the hot-reloading reloads the page, the list is there, rendered, full of items. When I press F5 to manually reload the page, it is no more. Everything seems to be alright with code though
So in the parent component I'm doing this:
<List ref="link_list"
:list="this.foo.links" //this is array
:child="'LinkFormItem'" //this is the name of the child component
:section_name="'Links'"
:defaults="{content: '', type: 'facebook'}" />
In the List component I get this:
Template
<li class="" v-for="item in datalist">
<component :is="child" :item="item" ></component>
<button v-on:click='remove(index++)' type="button" name="button" class='red button postfix small'>Remove</button>
</li>
Script
<script>
import Child1 from './Child1'
import Child2 from './Child2'
export default {
name: 'search',
props: ['child', 'list', 'defaults','section_name'], //it is received as 'list'
components: {
Child1, Child2
},
data () {
return {
index: 0,
datalist: [] //i'm trying to copy 'list' to 'datalist'
}
},
beforeMount: function () {
// i'm copying it
for(var k in this.list){
this.datalist.push(this.list[k])
}
},
methods: {
//and here I should change it the way I want
add: function () {
this.datalist.push(this.defaults)
},
getList () {
return this.datalist;
},
remove(index){
var datalist = [];
for(var k in this.datalist){
if(k != index) datalist.push(this.datalist[k]);
}
this.datalist = datalist;
}
}
}
</script>
I don't see any problems with my Script. What is going on??
#edit
Ok, some console.log later I found out what the problem seems to be. The HTTP Request is really taking much longer than the mounting of the component to happen. But when it happens, it is not triggering the update in the list component. Nothing is re-rendered and the list is empty.
Workaround
well I realised the problem was related to propagation. I made a few changes in the code to asure the parent component was updating and changing the model value. but the child component (the list component) was not receiving it.
then I gave up trying to understand why and did the following:
1- used the ref in the child component to force an update in the child component with $forceUpdate and then I was assigning the props to the model in the beforeUpdate event. It was causing an error: an re-rendering loop. The update caused a new update and so on. We could just use a flag to stop it.
2- Instead I just called a child method directly:
this.$refs.link_list.updateList(data.links);
I hate this approach because I think it's way too explicit. But it did the job. Then in the child component a new method:
updateList(list){
this.datalist = list;
}
3- The other possibility that passed through my mind was emitting an event. But I didn't try, too complicated
You can simply do like as follows
data () {
return {
index: 0,
datalist: this.list // to copy props to internal component data
}
},
Once you done about you need to apply data manipulation opertions on new this.datalist , not on this.list
If you don't want to mutate the original list array you can do this:
data () {
return {
index: 0,
datalist: Object.assign({}, this.list)
}
}
I think this will help you

how can component delete itself in Vue 2.0

as title, how can I do that
from offical documentation just tell us that $delete can use argument 'object' and 'key'
but I want delete a component by itself like this
this.$delete(this)
I couldn't find instructions on completely removing a Vue instance, so here's what I wound up with:
module.exports = {
...
methods: {
close () {
// destroy the vue listeners, etc
this.$destroy();
// remove the element from the DOM
this.$el.parentNode.removeChild(this.$el);
}
}
};
Vue 3 is basically the same, but you'd use root from the context argument:
export default {
setup(props, { root }){
const close = () => {
root.$destroy();
root.$el.parentNode.removeChild(root.$el);
};
return { close };
}
}
In both Vue 2 and Vue 3 you can use the instance you created:
const instance = new Vue({ ... });
...
instance.$destroy();
instance.$el.parentNode.removeChild(instance.$el);
No, you will not be able to delete a component directly. The parent component will have to use v-if to remove the child component from the DOM.
Ref: https://v2.vuejs.org/v2/api/#v-if
Quoted from docs:
Conditionally render the element based on the truthy-ness of the expression value. The element and its contained directives / components are destroyed and re-constructed during toggles.
If the child component is created as part of some data object on parent, you will have to send an event to parent via $emit, modify (or remove) the data and the child component will go away on its own. There was another question on this recently: Delete a Vue child component
You could use the beforeDestroy method on the component and make it remove itself from the DOM.
beforeDestroy () {
this.$root.$el.parentNode.removeChild(this.$root.$el)
},
If you just need to re-render the component entirely you could bind a changing key value to the component <MyComponent v-bind:key="some.changing.falue.from.a.viewmodel"/>
So as the key value changes Vue will destroy and re-render your component.
Taken from here