How to pass all props dynamically to child component - vuejs2

How to pass all props dynamically to child component? As an example consider a case:
// wrapper around a component my-wrapper.vue
<template>
<div>
<third-party-component />
</div>
</template>
third-party-component is a component which can accept number of attributes like value, disabled, clicked, etc. How I can use my-wrapper in way that whatever I passed as props to it it will be transferred to third-party-component like
<my-wrapper :value="myVal" :disabled="field.isDisabled" />

By default the attributes you add on to my-wrapper will be bound to the root element which is div. To avoid this set inheritAttrs option to false
Then you can bind all the attributes to using v-bind="$attrs" where $attrs contains parent-scope attribute bindings (except for class and style)
// wrapper around a component my-wrapper.vue
<template>
<div>
<third-party-component v-bind="$attrs"/>
</div>
</template>
<script>
export default{
inheritAttrs: false
}
</script>

I would set identical props to the wappers and use them in the template ...
But there is many ways, it easy to speak from parent to childs.
https://en.vuejs.org/v2/guide/components-props.html#Passing-Static-or-Dynamic-Props
https://v1.vuejs.org/guide/syntax.html#Shorthands
---- MyWrapper.vue
<template>
<div>
<third-party-component :value="value" :disabled="disabled" />
</div>
</template>
<script>
#import third-party-component from '../..? ./components/thirdPartyComponent.vue'
export default {
name: 'MyWrapper',
props: ['value', 'disabled'],
components: {
third-party-component // local component, you can use global
}
//... data, computed ...
}
</script>
----- MyAppli.vue
<template>
<my-wrapper :value="myVal" :disabled="field.isDisabled" />
</template>
<script>
import my-wrapper from '../..? ../components/MyWrapper.vue'
export default {
name: 'MyApp',
components: {
my-wrapper // local component, you can use global
} ....

Related

Vue: Reuse loading spinner template and logic

I've multiple (20+) pages where I need the following code:
<template>
<template v-if="isLoading">
<Spinner full size="medium" />
</template>
<template v-else>
<p>Page data</p>
</template>
</template>
<script>
export default {
computed: {
isLoading() {
return this.$store.getters['loader/isLoading'];
},
},
};
</script>
I don't want to rewrite this part everytime I need it so is there a way to create something like a higher order component with access to the computed method and a way to add the hoc to the script tag of the different vue files? Or another way to archive this?
I could recommend extending the spinner component where you want to show the spinner. I've created a boilerplate setup that show a very simple implementation of this approach here.
The main idea is to expose a default slot for you spinner component, and wrap the page component in that slot.
<template>
<div>
<Spinner v-if="isLoading" full size="medium" />
<!-- Slot for component data -->
<slot v-else></slot>
</div>
</template>
<script>
export default {
computed: {
isLoading() {
return this.$store.getters['loader/isLoading'];
},
},
};
</script>
Then in any component that you want to show the spinner:
<template>
<spinner>
<!-- Pass here the component content to be shown after the spinner is hidden -->
</spinner>
</template>
<script>
import Spinner from "./spinner";
export default {
name: "Page1",
extends: Spinner,
components: { Spinner }
};
</script>

Why Vue 2 remove every thing when using :is prop override component?

Please see this minimum example, I have a simple component called HelloWorld.vue
HelloWorld.vue
<template>
<div class="foo">bar</div>
</template>
When I using this component like this
App.vue
<template>
<HelloWorld />
</template>
<script>
import HelloWorld from "./HelloWorld.vue";
export default {
components: {
HelloWorld,
},
};
</script>
This rendered HTML looks like this
Rendered HTML
<div class="foo">bar</div>
However, when I add :is prop, rendered HTML changed
App.vue
<template>
<HelloWorld is="h2" />
</template>
<script>
import HelloWorld from "./HelloWorld.vue";
export default {
components: {
HelloWorld,
},
};
</script>
Rendered HTML
<h2></h2>
Why is this happening?
Is it possible to overwrite only the outer HTML tag just like the class and style prop?
is should be used together with the component element:
<component is="h2"></component>
<HelloWorld is="h2" /> efficiently renders h2 instead of HelloWorld.
In order for root element to be configurable, the component should provide this:
<template>
<component :is="tag" class="foo">bar</div>
</template>
<script>
export default {
props: {
tag: {
type: String,
default: 'div'
}
}
}
</script>

Vue: How to supply the layout component with dynamic props depending on router-view component's data

I'd like to have a layout component with a header component and a <router-view>, but the header component should receive different dynamic props depending on the data of the router-view component.
I'm agnostic to the way in which the layout architecture is being achieved: Either with nested routes or using a slot and passing the layout name via the route's meta field.
So, the header component should be passed in a dynamic prop from the view component - let's say Home.vue: If we go to route /home the router-view will be Home.vue and in Home.vue we have a reactive data field (i.e. scrolledDown) and the prop passed to the header component (i.e. theme) should depend on the value of this data field (scrolledDown).
The easiest way I can think of is using local state management (vuex or whatever), so that the view components (i.e. Home.vue) can store the reactive data and the layout component can grab it and pass it as prop to the header component. But maybe there are much better ways to accomplish this?
MainHeader.vue:
<template>
<div :class="`header--${theme}`">...</div>
</template>
<script>
export default {
props: ["theme"]
}
</script>
Layout.vue:
<template>
<div>
<main-header />
<router-view />
</div>
</template>
<script>
import MainHeader from "../MainHeader";
export default {
components: { MainHeader }
}
</script>
or alternatively:
App.vue:
<template>
<component :is="layout">
<router-view />
</component>
</template>
// script with `layout` data prop and layout component imports
Layout.vue:
<template>
<div>
<main-header />
<slot />
</div>
</template>
<script>
import MainHeader from "../MainHeader";
export default {
components: { MainHeader }
}
</script>
Home.vue:
<template>
<div>...</div>
</template>
<script>
export default {
computed: {
scrolledDown() { ... }
}
}
</script>

How to pass all events to parent in VueJS

Passing Props
In VueJS if you set inheritAttrs to false and use v-bind="$attrs" you pass all props not declarated in a component to its child. Is there a similar way to pass all events coming from child to its parent in VueJS?
Code Example
Wrapper
<template>
<child-component v-bind="$attrs" />
</template>
<script>
module.exports = {
inheritAttrs: false
...
}
</script>
Child
<template>
...
</template>
<script>
module.exports = {
...
props: {
prop1: Boolean,
prop2: Boolean
}
...
}
</script>
Parent
<template>
<wrapper :prop1="false" :prop2="true" />
...
</template>
To pass all events use
v-on="$listeners"
For Vue 3.x:
v-bind="$attrs"
see listeners removed
(thanks to AverageHelper)

Vue js how to use props values to v-model

I have two component namely App.vue and hello.vue
In App component I import the hello component and use props to pass relevant data to the hello component.
there I bind data which are took from the App component.
In my hello component I have a input box bind to the passed value.
My final goal is pass values as props to the hello component and change it and finally
pass that edited values to the backend using the save method.
How do I achive this?
This is what I have done up to now.
App.vue
<template>
<div id="app">
<hello-world :msg="'hello good morning'"></hello-world>
</div>
</template>
<script>
import helloWorld from "./components/HelloWorld";
export default {
components: {
helloWorld
}
};
</script>
hello.vue
<template>
<div>
<input type="text" :value="msg">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
In my hello component's input field v-model is not possible. I want something similar to the v-model.
You cannot use prop to bind to v-model. Child component is not supposed to modify prop passed by the parent component.
You will have to create a copy of prop in your child component if you wish to use prop with v-model and then watch prop like this:
<template>
<div>
<input type="text" #input="onInput" v-model="msgCopy">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
data() {
return { msgCopy: '' };
},
methods: {
onInput(newInputValue) {
this.$emit('msgChange', newInputValue);
}
}
watch: {
msg(newVal) {
this.msgCopy = newVal;
}
}
};
</script>
Also, notice the use of event handler #input to pass changed prop back to the parent component via event. As a syntax sugar, you can make your Hello component work as a custom form input control by adopting to v-model lifecycle.