I'm trying to create simple ToDo app using Ractive.js and Redux, but I ran into a problem with rendering more than one component on my page.
Javascript code:
import Ractive from 'ractive';
import template from './Home.html';
import './Home.less';
import { AddToDoForm, ToDoList } from '../';
export default Ractive.extend({
template,
data: {
message: 'This is the home page.',
},
components: {
AddToDoForm,
ToDoList
}
});
HTML of the component:
<AddToDoForm store={{store}} />
<ToDoList store={{store}} />
But only the first component is rendered. The store parameter I'm passing is the Redux store, but it doesn't work even without it.
I would add to verify defaults as a
...
components:{
AddToDoForm: AddToDoForm,
ToDoList: ToDoList
}
...
syntax examples (answer/31096635)
Related
I need to render a different layout for the same route for a specific URI with different components depending on the user being on mobile or in desktop.
I would like to avoid having route path checks in the PageCommon(layout component) to keep it clean.
The app has a main component taking care of the layout, it has different router-views where we load the different components for each page URI. This would be a normal route for that.
{
path: '',
component: PageCommon,
children: [
{
path: '',
name: 'Home',
components: {
default: Home,
header: Header,
'main-menu': MainMenu,
'page-content': PageContent,
footer: Footer,
'content-footer': ContentFooter
}
},
I can't change the route components property once the component is loaded so I tried to make a wrapper and pass the components dynamically.
{
path: 'my-view',
name: 'My_View',
component: () => import('#/components/MyView/ViewWrapper')
},
In /components/MyView/ViewWrapper'
<page-common v-if="isMobile">
<my-mobile-view is="default"></my-mobile-view>
<main-menu is="main-menu"></main-menu>
</page-common>
<page-common v-else>
<my-desktop-view is="default"></my-desktop-view>
<header is="header"></header>
<main-menu is="main-menu"></main-menu>
<footer is="footer"></footer>
</page-common>
</template>
I would expect that the components passed inside page-common block would be substituted on the appropriate , but is not how it works, and Vue just loads page-common component with empty router-views.
Is there any approach for this?
Note that I already tried using :is property for loading different components, but the problem then is on how to tell the parent to use this or that component for this page. This is the code for that:
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
data(){
return {
myView: null,
isMobile: this.detectMobile()
}
},
methods : {
getViewComponent() {
return this.isMobile ? 'mobile-view' : 'desktop-view';
}
},
created() {
this.myView = this.getViewComponent();
}
}
</script>
I could use this approach for each of the PageCommon router views, creating a component for each that does the above, but it looks like a very bad solution.
A computed method is all you need.
You should have this top level Logic in App.vue and the <router-view> should be placed in both DesktopView and MobileView.
// App.vue
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
computed: {
myView() {
return this.detectMobile() ? 'mobile-view' : 'desktop-view';
}
}
}
</script>
You may also want to consider code splitting by setting up Dynamic Components for those layouts since Mobile will load Desktop View because it is compiled into final build, register them globally as dynamic imports instead if importing them in MyView and then delete components also after doing the following instead, this way only the one that is needed will be downloaded saving mobile users their bandwidth:
// main.js
import LoadingDesktopComponent from '#/components/LoadingDesktopComponent '
Vue.componenet('desktop-view', () => ({
component: import('#/components/MyView/DesktopView'),
loading: LoadingDesktopComponent // Displayed while loading the Desktop View
})
// LoadingDesktopComponent .vue
<template>
<div>
Optimizing for Desktop, Please wait.
</div>
</template>
<script>
export default {
name: 'loading-component'
}
</script>
Routing logic will only be processed when <router-view> is available,this means you can delay the presentation of Vue Router, for example you can have :is show a splash screen like a loading screen on any URI before displaying a component in :is that contains <router-view>, only than at that point will the URI be processed to display the relevant content.
Problem: I am trying to programmatically register a component to be used in a slot in my Vue/Nuxt site. The component name is included in the data of the parent index.vue file, in this instance the component is named Projects. I am including it in a v-for template as the various objects in the 'modules' data array are iterated over. I had assumed this would be possible/easy from the dynamic component documentation and example however I have not managed to get it working in my case.
What I expect to happen: I expected the component to be registered and 'slotted' into the Module component correctly.
What actually happens: While I can see on the rendered view that the component is 'there', it is not in the correct place (i.e. slotted into the Module component). I also get a vue/no-unused-components error saying: The "Projects" component has been registered but not used.
I have read the documentation about component registration in modular systems but these seem to be for more complex cases than what I am trying to achieve. Any advice would be really helpful as I am totally stuck!
index.vue
<template>
<div>
<template v-for="module in modules">
<Module
:title="module.title"
:to="module.link"
/>
<component v-bind:is="module.slot" />
</Module>
</template>
</div>
</template>
<script>
import Module from '~/components/module/Module.vue'
import Projects from '~/components/module/slots/Projects.vue'
export default {
components: {
Module,
Projects
},
data () {
return {
modules: [
{
title: 'Work',
slot: 'Projects'
},
{
...
}
]
}
}
}
</script>
Edit: As a side note, I get the same error when registering the component with import like so:
components: {
Module,
'Projects': () => import('#/components/module/slots/Projects')
}
Module.vue
<template>
<div>
<h2>
{{ title }}
</h2>
<slot />
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
}
}
</script>
Projects.vue
<template>
<div>
<h3>Projects</h3>
</div>
</template>
<script>
export default {
name: 'Projects'
}
</script>
You use the self closure tag in your Module component.
This prevents your Projects component to be rendered within the slot.
Just replace:
<Module
:title="module.title"
:to="module.link"
/>
with:
<Module
:title="module.title"
:to="module.link"
>
and it should work.
I have a Vue component that imports and registers other components locally. I want to loop through components object and render them dynamically. I'm trying to achieve this like the following (inside .vue file):
<template>
<div v-for="component in components" class="component">
<component v-bind:is="component"></component>
</div>
</template>
<script>
import CarouselDefault from './carousel/CarouselDefault'
import AlertError from './alerts/AlertError'
import AlertInfo from './alerts/AlertInfo'
import AlertSuccess from './alerts/AlertSuccess'
import AlertWarning from './alerts/AlertWarning'
export default {
name: 'App',
components: {
CarouselDefault,
AlertError,
AlertInfo,
AlertSuccess,
AlertWarning
}
}
</script>
and I get this error message:
Property or method "components" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
The components property aren't avalibale in template builder of vue, you have to defien a computed or a property of data .
example:
computed:{
components(){
return [
CarouselDefault,
AlertError,
AlertInfo,
AlertSuccess,
AlertWarning
];
}
}
or
data(){
return {
components:[
CarouselDefault,
AlertError,
AlertInfo,
AlertSuccess,
AlertWarning
]
}
}
So currently I'm importing a component multiple times with different names.
import Page1 from "./Page/Page"
import Page2 from "./Page/Page"
import Page3 from "./Page/Page"
import Page4 from "./Page/Page"
I'm doing this as I want each instance to have its own set of properties, which then I use <keep-alive> to maintain their state.
I am also using them inside a <component :is="".
I was wondering if there was a way to create multiple instances without multiple import.
Codesandbox:
https://codesandbox.io/s/5x391j8y4x
you will notice that if I switch between the HelloWorlds, that the input will maintain their instances (input will change to what they were holding)
You don't need to use <component> because you only have one component type that you want to use: HelloWorld. <component> is only needed when you want to dynamically render different component types.
The reason why you require <keep-alive> is because the HelloWorld component has local state (msg) which will be lost once the component instance is destroyed.
You will need to use key to force Vue to instantiate a new instance of HelloWorld based on the page, and you need <keep-alive> to prevent each instance from being destroyed when you switch between pages.
Here's an example:
<ul>
<li
v-for="page in pages"
#click="currentPage = page"
:key="page.key">{{ page.title }}</li>
</ul>
<keep-alive>
<hello-world
:key="currentPage.key"
:title="currentPage.title"/>
</keep-alive>
import HelloWorld from './components/HelloWorld'
export default {
name: "App",
components: {
HelloWorld,
},
data() {
const pages = [
{
key: "home",
title: "Home"
},
{
key: "about",
title: "About"
},
{
key: "contact",
title: "Contact"
}
]
const currentPage = pages[0]
return {
currentPage,
pages
}
}
}
I'm using single file components and I'd like to know is there any way to extend imported component
For example I have Products.vue and Filter.vue component. Products.vue component contains string with table name it gets data from. Filter.vue to work should know table name it filters. Normaly I should pass that data with props from Products.vue to Filter.vue. But the problem is that there is multiple filters that becomes repetitive and I'd like to avoid it.
Will be good if there is some construction for extending imported component like:
Products.vue:
<template>
...
</template>
<script>
import Filter from './Filter.vue'
export default {
components: {
// something like
'extendedFilter': Filter.extend({ data() { return { table: this.table_name } } })
},
data() {
return {
table_name: 'test_table'
}
}
}
</script>