New to VueJS so hoping to get some advice on the best approach to take.
I have a parent containing multiple child components.
Once the child components have been created there is an event handler attached to each child
this.$parent.$on('save', this.save);
So that whenever I emit the following in the parent we call the child save method
this.$emit('save', block);
The problem that I'm seeing is that every child listen to events from this parent is reacting to this event.
Is there a better way to get a specific child to react to a parent click event?
Maybe you could try passing a prop with .sync modifier to a child, and then in the child component you should use computed property. This is an option but I don't know what are you trying to achieve, is there any specific reason that you must emit an event?
Read the vue.js dosumentation for props: https://v2.vuejs.org/v2/guide/components.html#Props
For what you are attempting, if possible, I'd use a Child Component ref and call a method on the child from the parent:
Vue.component('my-comp', {
template: "#my-comp-template",
props: ['name'],
methods: {
saveMyComp() {
console.log('Saved:', this.name);
}
}
})
new Vue({
el: '#app',
data: {
people: [{name: 'Bob'}, {name: 'Nelson'}, {name: 'Zed'}]
},
methods: {
saveChild(index) {
this.$refs.myComps[index].saveMyComp();
}
}
});
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<div v-for="(person, index) in people">
<button #click="saveChild(index)">saveMyComp</button>
<my-comp :name="person.name" ref="myComps"></my-comp>
</div>
</div>
<template id="my-comp-template">
<span> {{ name }} </span>
</template>
Remember the parent is already coupled to the child component -- since it knows it exists in the template declaration.
Related
i have a menu with many items. on item click I create a new Vue element (with Vuex store). The question is:
on close witch is outside the vue instance do I have to call this.$destroy or v-if="false" the root element.
<template>
<div v-if="closeVar">
....
</div>
</template>
<script>
export default {
...,
data() {
return {
closeVar: true
};
},
methods: {
onWindowsClose() {
this.$destroy() OR this.closeVar = false; ????
}
},
created() {
window[id + 'onWindowsClose'] = this.onWindowsClose;
}
}
</script>
From the $destroy docs:
In normal use cases you shouldn’t have to call this method yourself. Prefer controlling the lifecycle of child components in a data-driven fashion using v-if and v-for.
So the answer from the docs is that it's preferred to use v-if. Furthermore, you should place that v-if on the component's tag in the parent, not within the component on the root element:
Parent
<child v-if="closeVar"></child>
Otherwise, you'd only be removing the child's content rather than the entire component.
The difference there is, for example, the child's created hook would not be called again if you repopulated that particular instance with content (which would be possible because the component wouldn't have been destroyed.)
I would like to create a parent directive which shows or hides children based on the values of the children. To do this, i've taken the approach of a parent structural directive, and a child directive with values. For simplicity without the values:
<div *appParent>
<div appChild>1</div>
<div appChild>2</div>
<div appChild>3</div>
<div appChild>4</div>
</div>
To access the children, I use the following line in the parent directive:
#ContentChildren(AppChildDirective, { read: AppChildDirective, descendents: true }) children: QueryList<AppChildDirective>;
This query list is always empty. However, when I change it to a non-structural, it works fine. Stackblitz demo here
I assume this is due to the fact the structural directive creates a parent ng-template, which #ContentChildren then looks inside to find the original component, meaning that the query actually goes nowhere.
What approach can I take to access the children of the original component and not the template? Or do I need to take another approach to handle my requirements?
ContentChildren seem to not work on structural directives. However, this can be achived by injecting the parent directive in the child and then registering the child in the parent by calling a function.
#Directive({
selector: '[appChild]'
})
export class ChildDirective {
constructor(parent: ParentDirective) {
parent.registerChild(this);
}
}
#Directive({
selector: '[appParent]'
})
export class ParentDirective {
registerChild(child: ChildDirective) { /*...*/ }
}
Side notes
If you also want to be able to use the child directive without the parent directive, change the child's constructor like this to make the parent optional:
constructor(#Optional() parent: ParentDirective) {
parent?.registerChild(this);
}
You can also use this approach recursively by injecting a directive in its own constructor. If you do so, also add #SkipSelf() in the constructor to really get the parent:
#Directive({
selector: '[appRecursive]'
})
export class RecursiveDirective {
constructor(#Optional() #SkipSelf() parent: RecursiveDirective) {
parent?.registerChild(this);
}
registerChild(child: RecursiveDirective) { /*...*/ }
}
I have parent and child component communication, I want to fire up a method in child component that should open up a modal and pass the data. When I call a function from parent using ViewChild, the variable in the child method component returns null, however should have data inside variable. When I fire same function within the child component, the data is available, but its not a case when called from parent component. What is missing and why I don't see data in child component, from fire up from the parent component?
parent.html
<button (click)="openModal()">New</button>
<child><child>
parent.ts
import { FilterComponent } from './../filter/filter.component';
#ViewChild(FilterComponent) filterComponent: FilterComponent;
openModal(){
this.filterComponent.openModal(); // when fired from here, the function in the child component fires, but the variable inside it returns null
}
child.html
<button (click)="openModal()">New</button>
<modal><modal>
child.ts
openModal(){
modalVisible = true;
console.log(this.filter) returns null; when fired from parent component
console.log(this.filter) returns "filter data"; when fired within the component
}
From what I can tell you are currently selecting ALL FilterComponents inside your parentComponent. In order to use this you need to be more specific. You can change your ViewChild to select only one FilterComponent if that fits your use case.
Example:
parent.html
<button (click)="openModal()">New</button>
<!-- notice the #child attribute -->
<child #child><child>
Then in parent.component.ts
import { FilterComponent } from './../filter/filter.component';
#ViewChild("child") filterComponent: FilterComponent;
openModal(){
this.filterComponent.openModal(); // now that you're only selecting one element this function call should have access to this values.
}
You can also reference the relevant stack blitz i made here: https://stackblitz.com/edit/angular-lqmc8x
Good luck!
I think you need an EventEmitter.
parent.html
<child (messageEvent)="openModal($event)"></child>
child.ts
import { Component, Output, EventEmitter } from #angular/core;
export class...
#Output() messageEvent = new EventEmitter<string>();
openModal(val) { this.messageEvent.emit(val); }
child.html
<button (click)="openModal(val)">Click</button>
or something like that.
I think you should rethink your architecture here.
If the method is triggered from the parent component only, then put into the parent component
If the the method is triggered from both parent AND child component, then put into a service
I have two components, one inside the other one. I have a click event on the parent component that should change the data value of the child component.
<template>
<div>
.....
.....
<my-component
:options="options">
</my-component>
</div>
.....
.....
</template>
<script>
...
...
data(){
}
methods:{
clickEvent(array_from_child){
this.array = array_from_child; //array is in my-component
}
}
components:{
....
}
</script>
I want to trigger the clickEvent method on child's element change. how to do that?
It seems like you're asking two different questions.
First, accessing a child's data from its parent:
If possible, you should pass the array to the child component using the child's props. Then simply change the array in the parent and any changes will be reflected in the child. If the array really needs to be in the child, then you can define a method to retrieve it.
<template>
<child-component ref="child">
</child-component>
</template>
methods: {
onClick() {
const myArray = this.$refs.child.getMyArray();
}
}
And then, in the child
methods: {
getMyArray() {
return this.myArray;
}
}
Second, triggering a change in the parent from the child
In this case, Flame's answer is most idiomatic. Emit a custom event in the child and listen for that event in the parent.
When you are going from a child to a parent, you should use events:
{
methods: {
clickEvent()
{
this.$emit('click', mydata);
}
}
So in your parent element, you can then attach your own callback to the emitted event like so:
<template>
<my-child-component #click="theParentMethod" />
</template>
You could also use some reactivity by passing an object reference from the parent to the child, so if you change the object in the child, the parent can detect the changes. However this comes with some reactivity gotcha's, see https://v2.vuejs.org/v2/guide/reactivity.html .
Say I have a vuejs component called child-component which is added to a parent component as follows.
<child-component>
<div>Hello</div>
</child-component>
N.B. this is not the template of the child component. This is how the child component is added to the parent.
How can I get the innerHTML, i.e. <div>Hello</div> in the child component as a string?
Inside the component, child-component, the HTML would be available in the mounted lifecycle handler as this.$el.innerHTML. The parsed vnodes would be available from this.$slots.default.
console.clear()
Vue.component("child-component",{
template: `<div><slot/></div>`,
mounted(){
console.log("HTML", this.$el.innerHTML)
}
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<child-component>
<div>Hello</div>
</child-component>
</div>
You could look at using Vue Child Component Refs. That will let you access the child component's innerHTML from the parent component.
Just add a ref attribute to your child component. It can be anything you choose:
<child-component ref="mychildcomponent">
<div>Hello</div>
</child-component>
Then in the parent methods, you can do something like:
let childEl = this.$refs.mychildcomponent
That will set childEl the entire child component that you've referenced. To get the innerHTML youd just have to go a little further, and do something like this:
let childEl = this.$refs.mychildcomponent.$el.innerHTML
That should give you a string of the child component's innerHTML.
This might help: https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs
Alternatively if you want to get it from the child component itself, just do the following within a method:
let componentHTML = this.$el.innerHTML
Hope that helps.