I have the following structure of nested components.
<app-root>
<app-comp-1>
<app-comp-2>
</app-comp-2>
</app-comp-1>
<app-root>
I want to transclude any content into the last child (app-comp-2). So, I need something like this.
<app-comp-1>
<app-comp-2>
<ng-content></ng-content>
</app-comp-2>
</app-comp-1>
But in the app-root component is available only the app-comp-1 component. So, this is the place where I have to transclude my contents.
<app-root>
<app-comp-1>
<content-I-want-to-transclude></content-I-want-to-transclude>
</app-comp-1>
</app-root>
---------------------------
<app-comp-1>
<ng-content></ng-content>
<app-comp-2>
...
</app-comp-2>
</app-comp-1>
So I need a solution to get the content that has been transcluded into the first component and pass it down to the second one.
Plunker
This Github issue provides a nice solution to this problem, using the ngProjectAs
attribute.
In a situation with 2 layers of content projection, the first layer uses an ng-content
element with an ngProjectAs
attribute, with the next selector.
The second layer uses another ng-content
, and selects the value of the first layer's ngProjectAs
attribute:
Level 1 (parent component):
<ng-content select="my-component" ngProjectAs="arbitrary-selector"></ng-content>
Level 2 (nested child component):
<ng-content select="arbitrary-selector"></ng-content>
Usage:
<my-app>
<my-component>My Projected Content</my-component>
</my-app>
Resultant DOM:
<my-app>
<my-component>
<nested-component>My Projected Content</nested-component>
</my-component>
</my-app>
I had a similar problem, whereby I have a card component, which has a child card-header component as well as a selector for the card body.
The card-header component has a toggle button that dispatches actions for which cards are open / closed.
I then needed the ability to pass extra buttons into the card-header component from the parent component via the card component
I solved it by adding selectors at each level.
First I created a common card-header component, allowing me to have a single piece of code that handled toggling card content by dispatching actions to the NgRx store, which holds an array of cards that are hidden (using the supplied name input property).
The card-header component subscribes to the store and emits an event to the parent component when the toggled
status changes
@Component({
selector: 'po-card-header',
template: `
<div class="card-header">
<span class="text-uppercase">{{ header }}</span>
<div class="header-controls">
<ng-content select=[card-header-option]></ng-content>
<ng-content select=[header-option]></ng-content>
<span class="header-action" (click)="onTogglePanel()">
<i class="fa" [ngClass]="{ 'fa-caret-up': !collapsed, 'fa-caret-down': collapsed}"></i>
</span>
</div>
</div>
`
})
export class CardHeaderComponent implements OnInit, OnDestroy {
...
@Input() name: string;
@Output() togglePanel = new EventEmitter<boolean>();
collapsed$: Observable<boolean>;
collapsedSub: Subscription;
constructor(private store: Store<State>) {
this.collapsed$ = this.store.select(state => getPanelCollapsed(state, this.name);
}
ngOnInit(): void {
this.collapsedSub = this.collapsed$.subscribe(collapsed => {
this.collapsed = collapsed;
this.togglePanel.emit(collapsed);
});
}
.... unsubscribe on destroy.
}
Notice the header has 2 ng-content
sections.
The header-option
selector is for any other icons I want to add when I explicitly use this component e.g.
<div class="card">
<po-card-header>
...
<span header-option class="fa fa-whatever" (click)="doSomething()"></span>
</po-card-header>
...
</div>
My new icon will sit alongside the default toggle icon in the header.
The second card-header-option
selector is for root components, that use the card component, not the card-header component, but still want to pass extra icons into the header.
@Component({
selector: 'po-card',
template: `
<div class="card">
<po-card-header>
...
<ng-content select="[card-header-option] header-option></ng-content>
</po-card-header>
<div class="card-block">
<ng-content select="[card-body]"></ng-content>
</div>
</div>
`
})
...
The [card-header-option]
selector will select any elements with that attribute, then pass them down into the card-header component using the header-option
attribute
The final usage of my card component looks like this.
<div>
Some component that uses the card
<po-card
header="Some text to go in the card header"
name="a-unique-name-for-the-card">
<span card-header-option class='fa fa-blah header-action'></span>
<div card-body>
Some content that gets put inside the [card-body] selector on the card component.
</div>
</po-card>
</div>
The final result is that I can use my custom card component, and get the benefits of the toggle functionality that the card-header component gives, but also supply my own custom actions, which will also get rendered in the header
Hope you find this helpful :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With