I have a prop
<graph :active-metrics="$data.active_metrics"></graph>
In my child component I can access the value
export default {
template: '<div>{{activeMetrics}}</div>',
props: ['active-metrics'],
methods: {
What I need to do is trigger a method in the child whenever there is a change. How can I achieve this?
You can use v-bind to make the data from the parent flow down to the child.
In your case it would look something like this:
<graph v-bind:active-metrics="$data.active_metrics"></graph>
export default {
template: '<div>{{activeMetrics}}</div>',
props: ['active-metrics'],
watch: {
'active-metrics': function(){
alert('active-metrics updated');
}
}
See here for a working JSFiddle.
Related
I am trying to pass a function as property inside a component in Vue3. Here is the code:
// ExampleComponent.vue
<template>
Here goes component content...
</template>
<script>
export default {
name: ''
}
</script>
<script setup>
import { onMounted } from "vue"
const props = defineProps({
onLoad: {
type: Function,
default() {
return {}
}
}
})
onMounted(() => {
if (props.onLoad) { // This doesn't work and passes every time
props.onLoad()
}
})
</script>
Here is the parent component calling child ExampleComponent.vue. It may or may not pass the onLoad function.
<ExampleComponent \>
What I want is that call this function only if the property on-load is passed? Is there a way in Vue3 to check if the property is passed explicitly?
you can use watcheffect :
javascript
watchEffect(() => {
if(props.onLoad) props.onLoad()
});
once '''onLoad''' value changed, it will tigger.
I want to test a stencil component and configure a global variable in my test like this:
describe('my-component', () => {
const myVarMock = 1;
let page;
let shadowRoot: ShadowRoot;
beforeEach(async () => {
page = await newSpecPage({
components: [MyComponent],
html: `<my-component></my-component>`,
supportsShadowDom: true,
autoApplyChanges: true
});
shadowRoot = page.root.shadowRoot;
});
it('should test', () => {
page.rootInstance.myVar= myVarMock;
page.rootInstance.componentWillLoad();
page.rootInstance.render();
console.log(shadowRoot.innerHTML.toString());
const buttonElement = shadowRoot.querySelector('.my-button'); //is null because shadow root is empty
});
});
My Component only renders something, when myVar is set. In the console.log of my test, shadowRoot is always empty, although I explicitly call render() in the test and when I go through the render function in debug-mode it has a value for myVar and renders everything. But why is shadowRoot then empty and my buttonElement is undefined?
Component:
#Component({
tag: 'my-component',
shadow: true,
})
export class MyComponent{
public myVar;
componentWillLoad() {
...
}
render() {
return (
<Host>
{this.myVar? (
<div class="my-button"></div>
): null}
</Host>
)
}
}
Calling those life-cycle hooks like componentWillLoad and render manually does not do what I think you're expecting it to do. The Stencil runtime calls render and uses the return value (JSX) to eventually render your component. Manually calling render does not render or re-render your component. In fact, it doesn't do anything except returning some JSX to you but you're not doing anything with the return value.
I think the main issue in your case is that myVar is not declared as a property with the #Prop() decorator. So even though you have marked your class member as public and are able to change it from the outside, Stencil will not wire up anything for you regarding that prop. See https://stenciljs.com/docs/properties.
Instead, you'll have to define it as:
#Prop() myVar?: number;
That way it will cause Stencil to re-render your component every time you update the prop's value.
Your test case should just look like
it('should test', () => {
page.root.myVar = myVarMock;
console.log(shadowRoot.innerHTML.toString());
const buttonElement = shadowRoot.querySelector('.my-button');
expect(buttonElement).not.toBeNull();
});
I am currently doing my capstone project in our school. Currently I am using nuxt, vue, vuex, and vuetify for my csr and express for ssr.
In my code I want to fetch a data from v-for then pass to my component to query another data..
//my code in parent component
<template>
<v-list-group v-else v-for='(items, index) in halfpayments' :key="index">
<v-list-tile slot='activator'>
<v-list-tile-title><span class="subheading">{{items.ordnameto}}</span></v-list-tile-title>
</v-list-tile>
<v-list-tile>
<halfpay text='black--text' :ids='items.id'/>
</v-list-group>
</template>
<script>
import halfpay from '#/components/custom/fullHalfpay'
export default {
components: {
halfpay
}
}
//code in child component
<template>
<v-flex>
<v-list-tile v-for='(item) in data' :key="item.id">
<v-list-tile-content>
<v-list-tile-sub-title><span class="caption">Full payment date: </span><span :class="text">{{$dateFilter(item.paid_time)}}</span></v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
</v-flex>
</template>
<script>
export default{
props: {
ids: {
type: String,
required: true
},
text:{
type: String,
required: true
}
},
computed: {
data(){
return this.$store.getters.halffullpay(this.ids)
}
}
}
</script>
the output of it, the v-for will render all the data of the child component. Is there a way to get the data in specific way. The only way is to merge the 2 separate data but it complicates the logic in the long run because the child node is a conditional render which it need to satisfy a condition to apply the child component.
Use a computed property in your DOM. Your scripts will set this.dataFromAPI each time the API returns data, and your computed property (or multiple) will format that data as needed. Or the computed property of the child component is based off "input" props. (I couldn't quite tell from your code).
.map() it, .reduce() it, whatever you need.
You use the computed property name just like another data prop in your DOM, as if this.computedData was on your data().
ComboBoxComponent provides the method toggle that toggles the visibility of the comboBox popup. I want to display comboBox already opened. I have the following implementation:
datalist.component.html
<kendo-combobox #attributecombobox></kendo-combobox>
datalist.component.cs
#Component({
templateUrl: './datalist.component.html'
})
export class DatalistComponent implements OnInit {
#ViewChild('attributecombobox') public attributeCombobox: ComboBoxComponent;
}
I've tried setting the constructor:
constructor() {
this.attributeCombobox.toggle(true);
}
Doesn't work. I also tried the OnInit lifecycle hook:
ngOnInit() {
this.attributeCombobox.toggle(true);
}
It also does not work.
What is the right approach for this? Thanks in advance.
Update 1
Sorry, I didn't disclose all the code. The ComboBox actually has a *ngIf:
datalist.component.html
<kendo-combobox #attributecombobox *ngIf="setAttribute"></kendo-combobox>
datalist.component.cs
#Component({
templateUrl: './datalist.component.html'
})
export class DatalistComponent implements OnInit {
#ViewChild('attributecombobox') public attributeCombobox: ComboBoxComponent;
setAttribute = true;
ngOnInit() {
this.attributeCombobox.toggle(true);
}
}
So I think that I found an issue with kendo-combobox elements using *ngIf as you can see in this plunker that I forked from George K plunker (thanks George).
Update 2
I submitted an issue which was classified as a bug here.
The earliest possible place to open the component is in the ngOnInit (your second attempt). Calling the toggle method works just fine for me:
ngOnInit() {
this.combo.toggle();
}
Here is a runnable plunker:
http://plnkr.co/edit/ssbftD6hg3f7LM86CIPD?p=preview
Update
Indeed, the component will not be available in ngOnInit hook if a structure directive like ngIf is applied. Basically, this will desugar to
<ng-template [ngIf]="show">....combobox here... </ng-template>
As you've probably already noticed, the component inside the template will not be there on first init. The solution is to use a hook that will be called later in the component initialization, like AfterViewInit:
ngAfterViewInit() {
setTimeout(() => {
this.combo.toggle();
});
}
The updated plunkr demo can be found here - http://plnkr.co/edit/quLb3oeiVRJfqACqGKEK?p=preview
How to limit component events to it's parent/child components and prevent bubbling up.
I have the following two components:
ParentComponent.dart
#Component(
selector: "parent-component",
template: '<div>parent value: {{value}}<content></content></div>',
useShadowDom: false
)
class ParentComponent implements ScopeAware{
Scope _scope;
#NgAttr('value')
String value;
void set scope(Scope scope){
_scope = scope;
_scope.on('select-child').listen((ScopeEvent event){
value = event.data['value'];
});
}
}
ChildComponent.dart
#Component(
selector: "child-component",
template: '<div ng-click="select()">child: {{value}}</div>',
useShadowDom: false
)
class ChildComponent implements ScopeAware{
Scope _scope;
#NgAttr('value')
String value;
void select(){
_scope.parentScope.broadcast('select-child', {
'value': value
});
}
void set scope(Scope scope){
_scope = scope;
}
}
When one clicks on the child component, the parent updates its value.
But when i have more parent components they listen all to the same childs:
<!-- Parent 1 -->
<parent-component>
<child-component value="foo"></child-component>
<child-component value="bar"></child-component>
</parent-component>
<!-- Parent 2 -->
<parent-component>
<child-component value="herp"></child-component>
<child-component value="derp"></child-component>
</parent-component>
When i click the foo child-component of parent1 both parent components change their value to 'foo'.
I already tried playing with emit, broadcast. I know broadcast bubbles downwards to the leaf nodes and emit bubbles up. I also tried usind "scope.createChild()" but i think i miss something.
How can i create a scope in parent which is only visible to the child and vice versa?
Or how to use broadcast emit correctly?
When i understand the docs right i have to use emit() in child-component and not parentNode.broadcast() but i can't get it to work
Good question. Your confusion is coming from the fact that while components do create new scopes, those scopes are only available to the shadowDom (the html in the template: annotation).
In your example all 6 components - 2 parents and 4 children, create scopes that are child scopes of root scope. So when you are calling parentScope.broadcast you are firing event from rootScope down to all 6 scopes.
The easiest way to achieve the behavior you want is to directly inject ParentComponent into ChildComponent. Parent components are injectable to components in both their light and shadow DOMs.
class ChildComponent implements ScopeAware {
ParentComponent _p;
ChildComponent(this._p);
void select(){
_p.setValue(value);
}
...
}
Only downside is that makes the two components more tightly coupled to each other, which is ok if you are authoring both for your own application.