I have 2 components <parent> and <child>. I want to use them in recursive <ng-template>
let say for the sake of simplicity
<parent> component will contain <ul>
<child> component will contain <li>
here is what I have referenced https://gist.github.com/arniebradfo/5cf89c362cc216df6fc1d9ca4d536b72
here is how I have tried
<div>
<ng-template #recursiveList let-list>
<span *ngFor="let item of list">
<!-- commented {{item.title}} -->
<child [item]="item"/>
<!-- commented <ul *ngIf="item.children.length > 0"> -->
<template>
<parent [item]="item"/>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: item.children }"></ng-container>
</template>
<!-- commented </ul> -->
</span>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: list }"></ng-container>
</div>
but above code does not work properly
my expected result should look like below shown, for a parent with 2 childs
<span>
<parent>
<child/> // <-- child 1
<child/> // <-- child 2
</span>
with html it look like below
<span>
<ul>Parent</ul>
<li>child 1</li> // <-- child 1
<li>child 1</li> // <-- child 2
</span>
it must follow same nesting with any level.
please help me with a demo from https://gist.github.com/arniebradfo/5cf89c362cc216df6fc1d9ca4d536b72
Please help me thanks in advance !!!
main.component.html
<div>
<parent *ngFor="let item of list" [item]="item">
</div>
parent.component.html
<span>{{item.name}}</span>
<ul>
<child *ngFor="let child of item.children" [item]="child">
</ul>
child.component.html
<li>
<parent [item]="item">
</li>
Related
I was creating a reusable tabs component by watching tutorial. Having watched it, I figured out how it works. But, as for my project, I need to create tabs with heading containing a title that can be changed because this is a reusable component, so I have to change each heading's title for each new tab, but I haven't figured out how to do it. I need somehow to get title from component TabsWrapper that I added to my page
<div class="tab_header">{{title}}</div>
and then make this title change a text inside this div that is the main header of a TabsWrapper component.
<div class="tab_header">{{title}}</div>
My code:
The first one is the code out of component that I added to my homepage of the website.
<TabsWrapper>
<Tab title="Tab 1">Hello 1</Tab>
<Tab title="Tab 2">Hello 2 </Tab>
<Tab title="Tab 3">Hello 3</Tab>
<Tab title="Tab 4">Hello 4</Tab>
</TabsWrapper>
The second one is the code inside the component that is responsible for TabsWrapper
<template>
<div class="tabs">
<div class="tab_header"></div>
<ul class="tabs_header">
<li
v-for="title in tabTitles"
:key="title"
#click="selectedTitle = title"
:class=" {selected: title ==selectedTitle}"
>
{{ title }}
</li>
</ul>
<slot />
</div>
</template>
<script>
import { ref} from 'vue';
import { provide } from 'vue';
export default{
setup(props,{slots}){
const tabTitles=ref(slots.default().map((tab)=> tab.props.title))
const selectedTitle=ref(tabTitles.value[0])
provide("selectedTitle", selectedTitle)
return{
selectedTitle,
tabTitles,
}
}
}
</script>
This chunk of code takes each title from Tab
<Tab title="Tab 1">Hello 1</Tab>
And this chunk of code renders it out
<li
v-for="title in tabTitles"
:key="title"
#click="selectedTitle = title"
:class=" {selected: title ==selectedTitle}"
>
{{ title }}
</li>
I've tried to repeat the same technique, but it worked out, but I think that there's the better way to do it
<div class="tabs">
<div class="tab_header" v-for="headtitle in headTitles" :key="headtitle">{{headtitle}}</div>
<ul class="tabs_header">
<li
v-for="title in tabTitles"
:key="title"
#click="selectedTitle = title"
:class=" {selected: title ==selectedTitle}"
>
{{ title }}
</li>
</ul>
<slot />
</div>
</template>
<script>
import { ref} from 'vue';
import { provide } from 'vue';
export default{
setup(props,{slots}){
const tabTitles=ref(slots.default().map((tab)=> tab.props.title))
const headTitles=ref(slots.default().map((tab)=>tab.props.headtitle))
const selectedTitle=ref(tabTitles.value[0])
provide("selectedTitle", selectedTitle)
return{
selectedTitle,
tabTitles,
headTitles,
}
}
}
</script>
You can simply pass props in the script tag and directly access them using this keyword and prop name.
export default {
props: ['foo'],
created() {
// props are exposed on `this`
console.log(this.foo)
}
}
In the template tag like this
<span>{{ foo }}</span>
you don't need to use ref you can directly use v-for and loop over the array elements.
<li v-for="(item, index) in foo">
{{item}}
</li>
When a prop has the same name as a sub-element in a v-for directive. How to access it?
<template>
<ul>
<li v-for="{ id, name } in data" :key="id">
{{name}} and {{props.name}} // Doesn't work, and props name is shadowed here.
</li>
</ul>
</template>
<script setup lang="ts">
const props = defineProps({ data: Array, name: String });
</script>
Don't destructure it:
<template>
<ul>
<li v-for="item in data" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
In my vue-app I want to add an element inside a v-for-loop. So I tried to do this:
<li v-for="(slide, index) in slides" :key="index"
:class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
</li>
<li class="cursor-pointer item" v-if="slides.length === 5">foobar</li>
but this doesn't keep the "new" item inside the index e.g. I want the element to be inside the loop.
How can I solve that?
Do you mean that if slides.length === 5, then add your second <li> tag after each <li> tag?
If yes, please take a look at the following code
<template v-for="(slide, index) in slides">
<li
:key="slide.nodeName"
:class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
</li>
<li
:key="`${slide.nodeName}_${index}`"
class="cursor-pointer item"
v-if="slides.length === 5"
>
foobar
</li>
</template>
Please pay attention to the key value binding of the <li> tag at the same level
<el-row v-for="item in queryFolderList" :key="item.id">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-button-group>
<el-button type="primary" size="mini" #click="goToDetil(item.id)"
>{{ item.id }}</el-button
>
<el-button type="warning" size="mini" #click="submit(item.id)"
>{{ item.name}}</el-button
>
</el-button-group>
</el-col>
To include elements to the loop of other elements in vue.js, you need to include them into their tag. In your case you need to be aware of, that nesting <li> into <li> can cause problems. Try this:
<li v-for="(slide, index) in slides" :key="index"
:class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
<ul v-if="slides.length === 5">
<li class="cursor-pointer item">foobar</li>
</ul>
</li>
As the loop is on your first <li> element, everything between <li> and </li> of that element will be included into the loop. I added an <ul> around your second <li> for the nested list.
EDIT: Render second <li> if 5 slides in slides
So now that I think to know what you trying to achieve, let´s try to solve it. The second <li> has to be rendered after 5 loops done/if there are 5 slides in slides.
First you need to note, that v-if="slides.length === 5" only matches, if there are exactly 5 slides. So if you now provide something with a slides.length bigger than 5 or smaller, your <li> with foobar don´t show. Change it to v-if="slides.length > 5" instead. If <li> with foobar only should be shown if the length IS 5, then your code is working as it should.
But if you try to add <li> with foobar AFTER 5 items or even every 5 items, you could use this little trick:
<ul>
<span v-for="(slide, index) in slides" :key="index">
<li :class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
</li>
<li class="cursor-pointer item" v-if="(index + 1)%5 === 0">foobar</li>
</span>
</ul>
You shouldn´t include <span> in <ul>, but it should work. Now the loop runs for both <li> elements, but <li> with foobar only renders after each 5th element, due to v-if="(index + 1)%5 === 0".
Hope this helped you.
I have a structure like this:
<childs>
<child>
<ul>
<li v-for="item in currentData">#{{ item.name }}</li>
</ul>
</child>
</childs>
In the child component, I have a data property currentData.
// Child.vue
data: {
currentData: {}
}
For some reason, I am assigning value to this currentData prop, from the childs component (not from child).
// Childs.vue
child.currentData = data;
How do I make this currentData available to the slotted elements of <child>:
<ul>
<li v-for="item in currentData">#{{ item.name }}</li>
</ul>
The template for Child.vue is like this:
<template> <div><slot></slot></div> </template>
I tried something like this:
<template> <div><slot :current-data="currentData"></slot></div> </template>
I belive what you need is Scoped Slots.
For that you should explicitly pass (in the slot declaration at the template) what props you want to make available to the <slot> "user".
E.g. say you want to make a foo property available to the slot users of <child> (assuming childData property existi in <child>). You would do:
<!-- This is <child>'s template -->
<template> <div><slot :foo="childData"></slot></div> </template>
From that point on, whoever uses that <child> component can access that foo property by declaring slot-scope:
<child>
<ul slot-scope="slotProps">
<li>{{ slotProps.foo }}</li>
</ul>
</child>
Notice that the slot-scope is declared in the element that takes the place where the <slot> was.
Full demo:
Vue.component('children', {
template: '#children'
})
Vue.component('child', {
template: '#child',
data() {
return {
childData: "I am childData"
}
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<children>
<child>
<ul slot-scope="slotProps">
<li>{{ slotProps.foo }}</li>
<!-- <li v-for="item in currentData">#{{ item.name }}</li> -->
</ul>
</child>
</children>
</div>
<template id="children">
<div><slot></slot></div>
</template>
<template id="child">
<div><slot :foo="childData"></slot></div>
</template>
What if I wanted to add another element outside the <ul> element? Vue simply discards anything outside slot-scope.
This is not due to slot-scope, but to <slot>s in general.
Since child has only one <slot>, the first element you place within <child> is the one that will take the slot.
If you want to have multiple elements take the slot, you'll have to wrap them. E.g. in a <div>. But, if you don't want this wrapper element to be rendered, use a <template>. See demo below.
Vue.component('children', {
template: '#children'
})
Vue.component('child', {
template: '#child',
data() {
return {
childData: "I am childData"
}
}
})
new Vue({
el: '#app'
})
.child { border: 1px solid red }
<script src="https://unpkg.com/vue"></script>
<div id="app">
<children>
<child>
<template slot-scope="slotProps">
<ul>
<li>{{ slotProps.foo }}</li>
<!-- <li v-for="item in currentData">#{{ item.name }}</li> -->
</ul>
<span>howdy</span>
</template>
</child>
</children>
</div>
<template id="children">
<div><slot></slot></div>
</template>
<template id="child">
<div class="child"><slot :foo="childData"></slot></div>
</template>
I have code like this
<template *ngIf='mobile'>
<div class='wrapper'>
<div class='nav'>
<ng-content></ng-content>
</div>
</div>
</template>
<template *ngIf='desktop'>
<ng-content></ng-content>
</template>
However, Angular 2 renders ng-content just one time. Is there a way to get this case working properly without too much hacking?
update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
You can pass the content as a template, then you can render it multiple times.
<parent>
<template>
projected content here
</template>
</parent>
In parent
<ng-container *ngIf='mobile'>
<div class='wrapper'>
<div class='nav'>
<template [ngTemplateOutlet]="templateRef"></template>
</div>
</div>
</ng-container>
<template *ngIf='desktop'>
<template [ngTemplateOutlet]="templateRef"></template>
</template>
export class ParentComponent {
constructor(private templateRef:TemplateRef){}
}
You can also pass data to the template to bind with the projected content.
<ng-container *ngIf='mobile'>
<div class='wrapper'>
<div class='nav'>
<template [ngTemplateOutlet]="templateRef" [ntOutletContext]="{ $implicit: data}"></template>
</div>
</div>
</ng-container>
<ng-container *ngIf='desktop'>
<template [ngTemplateOutlet]="templateRef" [ntOutletContext]="{ $implicit: data}"></template>
</ng-container>
export class ParentComponent {
#Input() data;
constructor(private templateRef:TemplateRef){}
}
and then use it like
<parent [data]="data">
<template let-item>
projected content here {{item}}
</template>
</parent>
See also My own Angular 2 Table component - 2 way data binding