I'm trying to render or load components from api data. To explain more, let's say I've test-component, which I inject it directly in my parent component, works. But when I'm trying to save the component tag in database and run a ajax call, my component tag shows but doesn't work or rather load / render. Please help.
Return from my api:
{
"_id": "59411b05015ec22b5bcf814b",
"createdAt": "2017-06-14T11:16:21.662Z",
"updatedAt": "2017-06-14T12:41:28.069Z",
"name": "Home",
"content": "<test-comp></test-comp>",
"slug": "/",
"navName": "Home",
"__v": 0,
"landing": true,
"published": false
}
My parent component:
<template>
<div>
<test-comp></test-comp> // This works
<div v-html="page.content"></div> // But this doesn't :(
</div>
</template>
<script>
import { Api as defApi } from 'shared';
import test from './testComp';
export default {
data: () => ({
page: {}
}),
created() {
defApi.get('api/pages/landing')
.then((res) => {
this.page = res.data.body;
});
},
components: {
testComp: test
}
};
</script>
You can only specify plain HTML in the v-html tag. So, adding a component tag within the string passed to v-html won't work.
If you are simply trying to specify the component type, you can use a dynamic component. In your case, it might look something like this:
<template>
<div>
<component :is="dynamicComponent"></component>
</div>
</template>
<script>
import { Api as defApi } from 'shared';
import test from './testComp';
export default {
data: () => ({
dynamicComponent: null,
}),
created() {
defApi.get('api/pages/landing')
.then((res) => {
this.dynamicComponent = res.data.componentType; // e.g. "testComp"
});
},
components: {
testComp: test
}
};
</script>
Related
I'm trying to add a custom view with some administrative utilities to Spring Boot Admin. The idea is to implement these as endpoints in Springboot Admin and call these endpoints from my custom view, but I don't know how to make a call to the server itself.
When a custom view has parent: 'instances' it will get an axios client for connecting to the current instance, but since the view I'm building isn't tied to a specific instance it doesn't have this. I'm aware I can install axios as a dependency, but I'd like to avoid that if possible to reduce build times. Since SBA itself depends on axios it seems I shouldn't have to install it myself.
Based on this sample, this is what I have right now:
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: {
type: Array,
required: true
}
},
data: () => ({ exampleResponse: "No response" }),
async created() {
const response = await this.axios.get("example");
this.exampleResponse = response.response;
},
};
</script>
ExampleController.kt
#RestController
#RequestMapping("/example")
class ExampleController {
#GetMapping
fun helloWorld() = mapOf("response" to "Hello world!")
}
Console says that it can't read property get of undefined (i.e. this.axios is undefined). Text reads "GET /example: No response"
I'm not sure if this is the best way to do it, but it is a way.
I noticed that I do have access to the desired axios instance within the SBA.use { install(...) { } } block, and learned that this can be passed as a property down to the view.
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry, axios}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
// this is where we pass it down with the props
// first part is the name, second is the value
props: { "axios": axios },
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: { type: Array, required: true },
// this is where we retrieve the prop. the name of the field should
// correspond to the name given above
axios: { type: Object, required: true },
},
data: () => ({
exampleResponse: "No response",
}),
async created() {
// Now we can use our axios instance! And it will be correctly
// configured for talking to Springboot Admin
this.axios.get("example")
.then(r => { this.exampleResponse = r.data.response; })
.catch(() => { this.exampleResponse = "Request failed!" });
},
};
</script>
Based on the code given, it looks like you don't have axios initialized to how you want to use it.
You're calling it via this.axios but it's not in your component i.e
data() {
return {
axios: require("axios") // usually this is imported at the top
}
}
or exposed globally i.e
Vue.prototype.axios = require("axios")
You can simply just import axios and reference it.
<script>
import axios from 'axios';
export default {
created() {
axios.get()
}
}
</script>
I'm looking for a reusable way to display a full page loader (Sidebar always visible but the loader should cover the content part of the page) till all necessary api fetches has been done.
I've got a parent component LaunchDetails wrapped in a PageLoader component
LaunchDetails.vue
<template>
<PageLoader>
<router-link :to="{ name: 'launches' }"> Back to launches </router-link>
<h1>{{ name }}</h1>
<section>
<TabMenu :links="menuLinks" />
</section>
<section>
<router-view />
</section>
</PageLoader>
</template>
<script>
import TabMenu from "#/components/general/TabMenu";
export default {
data() {
return {
menuLinks: [
{ to: { name: "launchOverview" }, display_name: "Overview" },
{ to: { name: "launchRocket" }, display_name: "Rocket" },
],
};
},
components: {
TabMenu,
},
created() {
this.$store.dispatch("launches/fetchLaunch", this.$route.params.launch_id);
},
computed: {
name() {
return this.$store.getters["launches/name"];
},
},
};
</script>
PageLoader.vue
<template>
<Spinner v-if="isLoading" full size="medium" />
<slot v-else></slot>
</template>
<script>
import Spinner from "#/components/general/Spinner.vue";
export default {
components: {
Spinner,
},
computed: {
isLoading() {
return this.$store.getters["loader/isLoading"];
},
},
};
</script>
The LaunchDetails template has another router-view. In these child pages new fetch requests are made based on data from the LaunchDetails requests.
RocketDetails.vue
<template>
<PageLoader>
<h2>Launch rocket details</h2>
<RocketCard v-if="rocket" :rocket="rocket" />
</PageLoader>
</template>
<script>
import LaunchService from "#/services/LaunchService";
import RocketCard from "#/components/rocket/RocketCard.vue";
export default {
components: {
RocketCard,
},
mounted() {
this.loadRocket();
},
data() {
return {
rocket: null,
};
},
methods: {
async loadRocket() {
const rocket_id = this.$store.getters["launches/getRocketId"];
if (rocket_id) {
const response = await LaunchService.getRocket(rocket_id);
this.rocket = response.data;
}
},
},
};
</script>
What I need is a way to fetch data in the parent component (LaunchDetails). If this data is stored in the vuex store, the child component (LaunchRocket) is getting the necessary store data and executes the fetch requests. While this is done I would like to have a full page loader or a full page loader while the parent component is loading and a loader containing the nested canvas.
At this point the vuex store is keeping track of an isLoading property, handled with axios interceptors.
All code is visible in this sandbox
(Note: In this example I could get the rocket_id from the url but this will not be the case in my project so I'm really looking for a way to get this data from the vuex store)
Im introduce your savior Suspense, this feature has been added in vue v3 but still is an experimental feature. Basically how its work you create one suspense in parent component and you can show a loading when all component in any depth of your application is resolved. Note that your components should be an async component means that it should either lazily loaded or made your setup function (composition api) an async function so it will return an async component, with this way you can fetch you data in child component and in parent show a fallback if necessary.
More info: https://vuejs.org/guide/built-ins/suspense.html#suspense
You could use Events:
var Child = Vue.component('child', {
data() {
return {
isLoading: true
}
},
template: `<div>
<span v-if="isLoading">Loading …</span>
<span v-else>Child</span>
</div>`,
created() {
this.$parent.$on('loaded', this.setLoaded);
},
methods: {
setLoaded() {
this.isLoading = false
}
}
});
var Parent = Vue.component('parent', {
components: { Child },
data() {
return {
isLoading: true
}
},
template: `<div>
Parent
<Child />
</div>`,
mounted() {
let request1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
let request2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000);
});
Promise.all([ request1, request2 ]).then(() => this.$emit('loaded'))
}
});
new Vue({
components: { Parent },
el: '#app',
template: `<Parent />`
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
This may be considered an anti-pattern since it couples the parent with the child and events are considered to be sent the other way round. If you don't want to use events for that, a watched property works just fine, too. The non-parent-child event emitting was removed in Vue 3 but can be implemented using external libraries.
I am new to vue and trying to build my first vue app using nuxtjs. My problem right now has to do with architecture and folder structure.
In my other non-vue apps I always have a "services" directory where I keep all my code that makes http requests.
example under my services folder I will have a auth.ts file that contains code that posts login credentials to my API. This file/class returns a promise which I access from within my store.
I am trying to do this with vue using nuxtjs but I realised I am unable to access the axios module from anywhere aside my .vue file.
This is an example of how my code is now:
<template>
...
</template>
<script lang="ts">
import Vue from 'vue'
import ActionBar from '../../components/ActionBar.vue'
export default Vue.extend({
components: { ActionBar },
data() {
return {
example: ''
},
methods: {},
mounted() {
this.$axios.$get('/examples').then((res) => {
this.examples = res.data;
})
}
})
</script>
<style>
...
</style>
I would like to move the axios calls to their own files in my services folder. How do I do this?
what you can do is create a file inside the ./store folder, let's imagine, ./store/products.js, that will create a products store, inside, simple getters, mutations and actions:
export const state = () => ({
products: [],
fetchingProducts: false,
})
export const getters = {
getAllProducts(state) {
return state.products
},
hasProducts(state) {
return state.products.length > 0
},
isFetchingProducts(state) {
return state.fetchingProducts
},
}
export const mutations = {
setInitialData(state, products) {
state.products = products
},
setLoadingProducts(state, isLoading) {
state.fetchingProducts = isLoading
},
}
export const actions = {
async fetchProducts(context, payload) {
context.commit('setLoadingProducts', true)
const url = `/api/example/${payload.something}`
const res = await this.$axios.get(url)
context.commit('setInitialData', res.data)
context.commit('setLoadingProducts', false)
},
}
then in your .vue file, you can now use the store as:
<template>
<div>
<div v-if="isFetchingProducts"> loading... </div>
<div v-else-if="!hasProducts">no products found</div>
<div v-else>
<ul>
<li v-for="product in allProducts" :key="product.id">
{{ product.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
products: []
}
},
methods: {
...mapGetters({
isFetchingProducts: 'products/isFetchingProducts',
allProducts: 'products/getAllProducts',
hasProducts: 'products/hasProducts',
})
},
mounted() {
this.$store.dispatch('products/fetchProducts', {})
},
}
</script>
<style>
...
</style>
remember that:
to call a store action, you should use $store.dispatch()
to call a mutation, you should use $store.commit()
to call a getter, you should use $store.getter()
you can also use the Vuex helper mapGetters, mapActions and even mapMutations
You might also know that you can leverage the Plugins in Nuxt, that article has demo code as well so you can follow up really quick
I'm writing my first app using NUXT. I'm stuck at this issue for 2 days, so I decided to ask even if I think this is a question with a simple answer (it has to be).
On my project's layouts I have a default.vue and a home.vue
default.vue:
<template>
<div>
<!-- call Header component, this has an nav menu -->
<Header />
<!-- call Hero component -->
<Hero />
<nuxt />
<Footer />
</div>
</template>
<script>
import Header from '~/components/Header.vue'
import Footer from '~/components/Footer.vue'
import Hero from '~/components/Hero.vue'
export default {
components: {
Header,
Footer,
Hero
},
}
</script>
I want to display data from each page (title, subtitle and imageUrl). This data sometimes come from an apollo query request other times are defined on page file.
I've read the docs and searched here for the answer but I wans't able to implement it. I think it has to be done thought Vuex store but I don't know how.
Thank you
You can use nuxtServerInit action in vuex as one way to populate page data.
If you are using nuxt >= 2.12, you can use the new-and-improved fetch hook inside your layouts to make your apollo queries.
I DID IT!
So, it took a time to figure out, but I've learnt a lot during this process.
I'll let here some references I've used to come with this solution.
Very nice article on passing data through props, custom events and Vuex Store
CodeSandBox from Nuxt Documentation.
This question has a method to await apollo data and then render data
Let's go to the way I did it. I don't know if it's the best, but worked like a charm here.
I've created a hero.js file on my store folder:
data: {
title: "",
subtitle: "",
imgUrl: ""
}
})
export const mutations = {
setData (state, obj) {
state.data = {...state.data, ...obj}
}
}
export const getters = {
getHero (state) {
return state.data
}
}
Then on my default.vue I did:
<div>
<!-- call Header component -->
<Header />
<!-- call Hero component with his slots-->
<Hero>
<template v-slot:title>
<h1 class="title">{{ hero.title }}</h1>
</template>
<template v-slot:subtitle>
<h2 class="subtitle">{{ hero.subtitle }}</h2>
</template>
<template v-slot:heroImg>
<img :src="hero.imgUrl" />
</template>
</Hero>
<!-- This is where all yours pages will be -->
<nuxt />
<Footer />
</div>
</template>
<script>
// Import Header component
import Header from '~/components/Header.vue'
import Footer from '~/components/Footer.vue'
import Hero from '~/components/Hero.vue'
import { mapGetters } from 'vuex'
export default {
data(){
return {
//declaring hero Obj to contain hero data
hero: {
title: "",
subtitle: "",
imgUrl: ""
}
}
},
components: {
Header,
Footer,
Hero
},
//Getting getHero getter from hero.js and saving it to newHero
computed: mapGetters({
newHero: 'hero/getHero'
}),
//watching newHero to change and then updating this.hero Obj. This action will update the displayed data
watch: {
newHero: function (obj) {
this.hero = {...this.hero, ...obj}
}
}
}
</script>
Here I declare the variables and store than into Vuex Store:
<template>
...
</template>
<script>
export default {
data() {
return {
hero: {
title: "Awesome Static title",
subtitle: "Awesome static subtitle"
}
}
},
//Saving the declared Hero to Vuex Store, then my default.vue will be able to get it through this.$store.getters
mounted() {
this.$store.commit("hero/setData", this.hero);
},
}
</script>
At some pages the title are fetched from the database (GraphQL using Apollo). Then I did:
<template>
...
</template>
<script>
import getLojaInfo from '~/apollo/queries/loja/loja.gql'
export default {
//declaring data
data() {
return {
lojas: Array,
loading: 0,
hero: {
title: "",
subtitle: "",
imgUrl: ""
}
}
},
//making the query
apollo: {
lojas: {
$loadingKey: 'loading',
prefetch: true,
query: getLojaInfo,
variables () {
return { slug: this.$route.params.singleLoja }
},
//it will wait for query result that and then populate this hero, it will update the hero title, subtitle and image
result(ApolloQueryResult, key) {
this.hero.title = ApolloQueryResult.data.lojas[0].name
this.hero.subtitle = ApolloQueryResult.data.lojas[0].description
this.hero.imgUrl = ApolloQueryResult.data.lojas[0].logo.url
//then commit it to Vuex Store
this.$store.commit("hero/setData", this.hero);
}
},
},
}
</script>
Thank you all, I would appreciate contributions to my code.
I'm building a nuxt app to consume the wp rest API. In my fetch method I fetch information about needed components. I can't figure out how to then import all the components and render them. I've tried several methods, but I can't see to make it work.
Here's what works:
<component :is="test" :config="componentList[0]"></component><br>
export default {
async fetch({ store, $axios }) {
await store.dispatch("getPageBySlug", "home");
},
computed: {
test() {
return () => import('~/components/HeroIntro');
}
}
};
Ok so this is easy, nothing special - I could now import the component based on the slug etc. But I need to render multitple components and therefor im doing this:
<component
v-for="component in componentList"
:key="component.acf_fc_layout"
:is="component.acf_fc_layout"
:config="component">
</component>
along with this
export default {
async fetch({ store, $axios }) {
await store.dispatch("getPageBySlug", "home");
},
computed: {
page() {
return this.$store.getters.getPageBySlug("home");
},
componentList() {
return this.page.acf.flexible_content;
},
componentsToImport() {
for(const component of this.componentList) {
() => import('~/components' + component.acf_fc_layout);
}
}
}
};
All I'm getting is
Unknown custom element: HeroIntro - did you register the
component correctly? For recursive components, make sure to provide
the "name" option
How do I archieve what im trying?
edit:
So, after a lot of trying, I could only make it work with using an extra component, "DynamicComponent":
<template>
<component :is="componentFile" :config="config"></component>
</template>
<script>
export default{
name: 'DynamicComponent',
props: {
componentName: String,
config: Object
},
computed: {
componentFile() {
return () => import(`~/components/${this.componentName}.vue`);
}
}
}
</script>
Now in Index.vue
<template>
<main class="container-fluid">
<DynamicComponent
v-for="(component, index) in componentList"
:key="index"
:componentName="component.name"
:config="component"
/>
</main>
</template>
<script>
export default {
components: {
DynamicComponent: () => import("~/components/base/DynamicComponent")
}
I am not sure yet if this is optimal - but for now it works great - any input / opinions would be great!