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
Related
I can not loop through the $slots object in Vue 3 to pass all slots from parent to child, the $slots object seems empty in the child component.
How can I loop through the $slots object to pass all parent slots to the child component?
I get this error when I run the code:
TypeError: Cannot read properties of null (reading 'key')
Here is a sandbox about my problem and you can uncomment line 5 to see the complete result:
https://codesandbox.io/s/blazing-bush-g7c9h?file=/src/pages/Index.vue
GitHub sample:
https://github.com/firibz/vue3slots
parent:
<system-input filled v-model="text" label="input">
<template v-slot:before>
<q-icon name="mail" />
</template>
</system-input>
child:
<div class="row justify-center">
<q-input v-model="componentValue" v-bind="$attrs" style="width: 250px">
<template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope"/>
</template>
</q-input>
<q-separator class="full-width" color="secondary" />
<div class="bg-negative full-width q-pa-lg">slots: {{ $slots }}</div>
<div class="bg-warning full-width q-pa-lg">slots: {{ $slots.before }}</div>
</div>
You have to use Object.keys($slots) in order to use slots on v-for.
<q-input v-model="componentValue" v-bind="$attrs" style="width: 250px">
<template v-for="(slot, index) of Object.keys($slots)" :key="index" v-slot:[slot]>
<slot :name="slot"></slot>
</template>
</q-input>
I have a component that defines a set of slots dynamically from props like this:
<template v-for="(field, index) in fields">
<div
v-if="!field.hide"
:key="index"
class="my-class"
>
<slot :name="getSlotName(field.name, 'label')" />
</div>
</template>
And I have a component WrapperComponent that uses template to fill those slots:
<slotted-component :fields="[{name: 'f1'}]">
<template #f1>
Some content here
</template>
</slotted-component>
And I want to be able to override the content of <template #f1> from the parent component of the WrapperComponent like
<WrapperComponent>
<template #f1>
Some other content
</template>
</WrapperComponent>
I tried to use a PassThroughTemplate component with this template:
<template>
<template v-slot:[name]>
<slot
:name="name"
v-bind="$attrs"
/>
</template>
</template>
but it gives me a compilation error if not compiling at runtime (only eslint yells in this case). I want to use render function, but this code doesn't render anything:
render (h, ctx) {
return h(
'template',
{slot: ctx.props.name},
h('slot', {name: ctx.props.name})
)
}
and i don't know how to use it properly. So, is there a way to do that or another way to achieve template replacement?
I'm trying to create a wrapper for the bootstrap-vue Table component. This component uses slots to define cell templates, like that:
<b-table :items="itemsProvider" v-bind="options">
<template v-slot:cell(id)="data">
///...here goes the template for the cell's of itens key "id"
</template>
</b-table>
So, the wrapper i'm creating is like this:
<div>
<b-table :items="itemsProvider" v-bind="options" >
<slot></slot>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
/>
</div>
And i want to call this component like this:
<TableAjax :options="options">
<template v-slot:cell(id)="data">
///...here goes the template for the cell's of itens key "id"
</template>
</TableAjax>
But, since the slots needed on the b-table component are named, i'm having a hard time passing it from the wrapper.
How can i do that?
Passing slots to a child component can be done like this:
<template>
<div>
<b-table :items="itemsProvider" v-bind="options" >
<template v-slot:cell(id)="data">
<slot name="cell(id)" v-bind="data"></slot>
</template>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
/>
</div>
</template>
But since you may not know the slot names ahead of time, you would need to do something similar to the following:
<template>
<div>
<b-table :items="itemsProvider" v-bind="options" >
<template v-for="slotName in Object.keys($scopedSlots)" v-slot:[slotName]="slotScope">
<slot :name="slotName" v-bind="slotScope"></slot>
</template>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
/>
</div>
</template>
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'm familiar with the concept of ngTransclude via AngularJS and this.props.children via ReactJs, however does Aurelia support transclusion, that is, the notion of inserting, or transcluding, arbitrary content into another component?
Transclusion in AngularJS (https://plnkr.co/edit/i3STs2MjPrLhIDL5eANg)
HTML
<some-component>
Hello world
</some-component>
JS
app.directive('someComponent', function() {
return {
restrict: 'E',
transclude: true,
template: `<div style="border: 1px solid red">
<div ng-transclude>
</div>`
}
})
RESULT
Transclusion in ReactJs (https://plnkr.co/edit/wDHkvVJR3FL09xvnCeHE)
JS
const Main = (props) => (
<SomeComonent>
hello world
</SomeComonent>
);
const SomeComonent = ({children}) => (
<div style={{border: '1px solid red'}}>
{children}
</div>
);
RESULT
Several ways to do transclusion: Official docs
1. content slot <slot></slot>
The <slot> element serves as a placeholder in a component's template for arbitrary content. Example:
some-component.html
<template>
<div style="border: 1px solid red">
<slot></slot>
</div>
</template>
usage:
<template>
<require from="some-component"></require>
<some-component>
hello world
</some-component>
</template>
result:
2. named slots
A component can contain several replaceable parts. The user of the component can replace some or all of the parts. Parts that aren't replaced will display their default content.
blog-post.html
<template>
<h1>
<slot name="header">
Default Title
</slot>
</h1>
<article>
<slot name="content">
Default content
</slot>
</article>
<div class="footer">
<slot name="footer">
Default footer
</slot>
</div>
</template>
usage:
<template>
<require from="blog-post"></require>
<blog-post>
<template slot="header">
My custom header
</template>
<template slot="content">
My custom content lorem ipsum fla fla fla
</template>
<template slot="footer">
Copyright Megacorp
</template>
</blog-post>
</template>
3. template parts
The slots spec has limitations: http://aurelia.io/hub.html#/doc/article/aurelia/templating/latest/templating-content-projection/5
Use template-parts for dynamically generated slots: https://github.com/aurelia/templating/issues/432
Yes, Aurelia supports the concept of transclusion through use of the <content /> component. Per the below, any content nested within <some-component> be it HTML, a String, or another component, will be rendered within this component.
app.js
export class App {}
app.html
<template>
<require from="some-component"></require>
<some-component>
hello world
</some-component>
</template>
some-component.js
export class SomeComponent {}
some-component.html
<template>
<div style="border: 1px solid red">
<content />
</div>
</template>
RESULT
UPDATE: USE SLOT INSTEAD OF CONTENT
// Actual component
<your-component>
Just some content
</your-component>
// Template of the component
<template>
<div class="some-styling">
<slot></slot> // <-- "Just some content" will be here!!
</div>
</template>