Conditional Root Tag on Vue Component - javascript

My Question
I have a Vue component that renders content like so:
<template>
<div class="item">
<h1>{{ title }}</h1>
<p>{{ contents }}</p>
<!-- Lot's of other stuff... -->
</div>
</template>
<script>
// export default...
</script>
<style lang="scss">
// style...
</style>
Note the contents within the div...
In some circumstances, I need to change <div class="item"> to <a class="item">. With that in mind, is there a way to conditionally change the tag (e.g. a, div) for the root element of a Vue component?
Research
I have searched around online and was able to find something about using the render function like so:
render (createElement) {
return createElement(this.tag, {}, this.$slots.default);
}
The issue I have with the above is that it implies that I need two separate components, for example; Item.vue and ItemTag.vue. Surely there is a way to do this with one component?

I believe you could use is:
<div :is="useA ? 'a' : 'div'">
...
</div>
This isn't quite what the docs suggests it's for but it does seem to have the desired effect.
https://v2.vuejs.org/v2/api/#is
Using a render function instead wouldn't necessarily require you to have two components but it would need you to rewrite your entire template as a render function.

Related

Nuxtjs: How to call component inside another component?

I understand paradigm "Page-component" but what if I have a page that renders component, how do I call another component inside this component? Currently nuxtjs does not allow me do it. I can not stick to standart "page-component" scheme as I am bulding cart which calls cart-items.
Say If a cart component which is called by page looks like this, how would it call cart-item component inside it?
<!---- cart component called from index.vue --->
<template>
<div>
<Cart-item></Cart-item> < ---------- This doesn't work.
</div>
</template>
<script>
export default {
props: ['items']
}
</script>
I managed it the standard way:
<template>
<div>
<CartItem></CartItem>
</div>
</template>
<script>
import CartItem from '../components/Cart-item'
export default {
props: ['items']
}
</script>
Since nuxtjs auto-registers all components wonder if there is more graceful way.
EDIT: as promised, here is an example on how to pass some content to a component from another one thanks to slots. This is totally working in any Nuxt page ofc.
NestedContent.vue
<template>
<div>
<p>Here is the NestedContent component and below is a slot passed to ParentWithSlots' component</p>
<hr />
<parent-with-slots>
<!-- <template #default> // this one can be omit since we do use the default slot here -->
<p>This content is inserted into the component ParentWithSlots</p>
<!-- </template> -->
</parent-with-slots>
</div>
</template>
ParentWithSlots.vue
<template>
<div>
<p>xxxxxxxxxxx ParentWithSlots' content before slot xxxxxxxxxxx</p>
<slot>Default content in case none is provided</slot>
<p>xxxxxxxxxxx ParentWithSlots' content after slot xxxxxxxxxxx</p>
</div>
</template>
Here is how it looks
PS: you may also give a try to layouts, it can be useful for overall positioning of some of your components visually.
If your components are in the components directory, you can set components: true in your nuxt.config.js and have access to it pretty much anywhere without any additional step with the <cart-item></cart-item> syntax.
More details here: https://nuxtjs.org/blog/improve-your-developer-experience-with-nuxt-components/

Render external element inside Vue component

I have an external div that I need to render inside my Vue app. I'm trying to use a slot, like but that's a no go as nothing renders.
Any ideas?
Goal is to have HTML like this (Vue mounts on #app):
<div id="app" data-slot-header="#header"></div>
<div id="header">
<h1>Title here</h1>
</div>
Then the Vue component
<template>
<div>
<slot name="header"></slot>
</div>
</template>
You can use a dynamic <component> and refer to your #header element as a template reference.
For example
new Vue({
data: () => ({
headerComponent: {
template: '#header' // refer to template element by selector
}
}),
}).$mount('#app')
#app:before,#header:before{position:absolute;top:0;right:0;color:rgba(1,1,1,.5);font-size:.8rem}#app{border:1px solid #666;position:relative}#app:before{content:'Vue app'}#header{position:relative;opacity:.5}#header:before{content:'Original header'}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<p>Dynamic component rendered here 👇</p>
<component :is="headerComponent"></component>
</div>
<div id="header">
<h1>Title here</h1>
</div>
Slots are mainly used with reusable Vue components so that the parent component can render custom stuff inside designated sections of the child. The root component does not have a parent, so it doesn't make sense to use slots for this.
Why can't you just hard-code the div in the template? Or do you need it to be dynamic; will you be swapping out the header contents in some situations? Please provide more information about what your use-case is, otherwise my answer is "just hard-code it".
Take a look at portal-vue. It allows child components to render templates anywhere in the DOM. This might work for your situation.

ng component not useful

I was learning the ng-content. Why need to use it when we can easily write like this
with ng-content
menu.component.html
<div>
<app-message> <h1>Laptop</h1></app-message>
</div>
message.component.html
<div>
<ng-content></ng-content>
<p>something text to be display</p>
<button > Submit</button>
</div>
without ng-content
menu.component.html
<div>
<h1>Laptop</h1>
<app-message> </app-message>
</div>
message.component.html
<div>
<p>something text to be display</p>
<button > Submit</button>
</div>
Using <ng-content> allows you to create flexible reusable components.
For example, a custom app-modal component:
<div class="modal">
<div class="modal-title">
{{title}}
</div>
<div class="modal-body">
<ng-content></ng-content>
</div>
</div>
This is very powerful, and only the start of what you can achieve with <ng-content>
A core prinpiple of software engineering is code reuse (DRY). With this we can bundle all of the logic relating to a component into one component, and inject the content into it.
This is like asking the question
Why not just declare your CSS styles inline instead of in CSS classes?
It is possible, but unmaintainable, and we have evolved beyond that.
Your example is fairly trivial, but it is still useful if you wanted to change style or behaviour based on some injected logic.
There exist some situations when is necessary, maybe no in simple cases but when the app grows up and you have to insert a custom component inside another is very usefully
As you can see from your own example, without using ng-content, the parent component suddenly is partially responsible for the child's layout. It'll get messy real quick, the quicker the more complex the layout gets.
It's useful for displaying components inside of other components, so the components can be reusable. For example, if you want to display some text on a few pages, you call the component with the text inside of the other components that you want the text to be displayed on. It could also be useful for showing different information but styled in the same format. E.g.
if your ng-content already has the information this is an example of how it would be used:
menu.component.html
<ng-content></ng-content>
message.component.html
<ng-content></ng-content>
This is good because you can copy and paste the exact component into another one without having to rewrite the code
If your ng-content is looking for a data source for information to pass into it
<ng-content [data]='data'></ng-content>
This is good because you can recreate the component inside of another component but with different data inside of it.
If you've ever used react, you pass data into it in a similar way here as you would with react props, but instead of props in angular, it will be an #input field. Here is some example code
test.component.html
<ng-content [data]='THIS IS THE DATA'></ng-content>
This is the actual component, as you can see, it is looking for a data source
ng-content.component.html
<p>The data we are looking for is {{data}} </p>
ng-content.component.ts - this says that when the component is called, it is looking for an input called 'data' and the type has to be a string
#Input() data: string;
We would then see the test.component.html displayed like this:
The data we are looking for is THIS IS THE DATA

How can I use template tag inside slot in Vue.js?

I have a template that includes some HTML along with some script tags. I want to use it inside my vue component as a template that later I will place into the DOM and I would like it to be placed as is, so my script tags are also executed.
I would like to keep the template inside the component tag and use a slot.
e.g.
<amazing-component>
<template>
<p>This is the content</p>
<script>
console.log("I want to be executed as long as someone puts me in the DOM!");
</script>
</template>
</amazing-component>
<script type="text/x-template" id="component-template">
<slot></slot>
</script>
But the problem is that Vue removes the template and the script tags and then replaces the slot tag.
What I've desperately tried so far and didn't work:
Put the template inside another template - the template just disappears.
Wrap the slot with a template - the template just disappears.
Use a <script type="text/x-template" > - that was an epic failure.
Get the slot from $slots - It is a VNode that doesn't have template/script tags.
The biggest problem is that the front-end doesn't have a clue of what the html inside the template tag looks like. It is just some html that is rendered on the server, some immutable legacy code that has to be inserted as-is in the page in order to work.
Of course I can put the template tag outside of the component and fetch it another way, but I would like to have it inside the component to keep it a bit more tidy and elegant.
While Vue consumes template tags in its own compiling, you can still force it to emit HTML template tags by using <component is="template">
var app = new Vue({
el: '#app',
template: `
<div>
<h1>No template:</h1>
<template>
test
</template>
<p>More code (check the page source)</p>
<h1>Yes template: </h1>
<component is="template">
test
</component>
<p>More code(check the page source)</p>
<h1>Yes script: </h1>
<component is="script">
console.log('Scrpt test');
</component>
<p>More code(check the console)</p>
</div>
`,
data: {}
})
<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app"></div>
That's not how Vue works. <script> tags do not belong in the <template> and are omitted for a reason. If you want something to be executed on page load, use a coresponding hook
To execute server generated JS code use eval()
Parent.vue
<template>
<child script="console.log('something')"></child>
</template>
Child.vue
<template>
</template>
<script>
props: ['script'],
created() {
eval(this.script)
}
</script>

Communicating variable in vue.js between components

I am building app using laravel and vue. I have navbar, currently it looks like:
<template>
<nav class="navbar">
<p>{{msg}}</p>
</nav>
</template>
And I use it like here:
<body class="">
<div id="app">
<div class="">
<navbar></navbar>
#yield('content')
</div>
</div>
</body>
In yield I am loading another components, so I have navbar and another component together. Now I want to override that {{msg}} variable from navbar in another components. In every component that variable will be diferent.
I do not know how to override it in components and from {{msg}} do some text. Can you help me? (That code above is all what I have)
If you want to use msg in other components, then you need to use prop
Use like:
props: ['msg'],
Then, you need to bind it like:
<component-name :msg="msg"></component-name>
In your component, you can take it like:
<template>{{ msg }}</template>
Hope you understand!
Components can be communicate with props. You can transfer the data to another components and you can use if statement.
https://v2.vuejs.org/v2/guide/components.html#Props

Categories