Reputation: 6652
I'm trying to use @ContentChildren
to pick up all items with the #buttonItem
tag.
@ContentChildren('buttonItem', { descendants: true })
This works when we have the ref item directly in the parent component.
<!-- @ContentChildren returns child item -->
<parent-component>
<button #buttonItem></button>
<parent-component>
But, if the element with the #buttonItem
ref is wrapped in a custom component, that does not get picked by the @ContentChildren
even when I set the {descendants: true}
option.
<!-- @ContentChildren returns empty -->
<parent-component>
<child-component-with-button-ref></child-component-with-button-ref>
<parent-component>
I have created a simple StackBlitz example demonstrating this.
Upvotes: 9
Views: 3856
Reputation: 21
I had the same issue. We are using Kendo Components for angular. It is required to define Columns as ContentChilds of the Grid component. When I wanted to wrap it into a custom component and tried to provide additional columns via ng-content it simply didn't work.
I managed to get it working by resetting the QueryList of the grid component AfterViewInit of the custom wrapping component.
@ViewChild(GridComponent, { static: true })
public grid: GridComponent;
@ContentChildren(ColumnBase)
columns: QueryList<ColumnBase>;
ngAfterViewInit(): void {
this.grid.columns.reset([...this.grid.columns.toArray(), ...this.columns.toArray()]);
this.grid.columnList = new ColumnList(this.grid.columns);
}
Upvotes: 2
Reputation: 10859
One option is re-binding to the content child.
In the template where you are adding the content child you want picked up:
<outer-component>
<content-child [someBinding]="true" (onEvent)="someMethod($event)">
e.g. inner text content
</content-child>
</outer-component>
And inside of the example fictional <outer-component>
:
@Component()
class OuterComponent {
@ContentChildren(ContentChild) contentChildren: QueryList<ContentChild>;
}
and the template for <outer-component>
adding the <content-child>
component, re-binding to it:
<inner-template>
<content-child
*ngFor="let child of contentChildren?.toArray()"
[someBinding]="child.someBinding"
(onEvent)="child.onEvent.emit($event)"
>
<!--Here you'll have to get the inner text somehow-->
</content-child>
</inner-template>
Getting that inner text could be impossible depending on your case. If you have full control over the fictional <content-child>
component you could expose access to the element ref:
@Component()
class ContentChildComponent {
constructor(public element: ElementRef<HTMLElement>)
}
And then when you're rebinding to it, you can add the [innerHTML]
binding:
<content-child
*ngFor="let child of contentChildren?.toArray()"
[someBinding]="child.someBinding"
(onEvent)="child.onEvent.emit($event)"
[innerHTML]="child.element.nativeElement.innerHTML"
></content-child>
You may have to sanitize the input to [innerHTML]
however.
Upvotes: 0
Reputation: 11081
Doesn't appear to be a timeline for a resolution of this item via github... I also found a comment stating you cannot query across an ng-content boundary.
https://github.com/angular/angular/issues/14320#issuecomment-278228336
Below is possible workaround to get the elements to bubble up from the OptionPickerComponent
.
in OptionPickerComponent
count #listItem
there and emit the array AfterContentInit
@Output() grandchildElements = new EventEmitter();
@ViewChildren('listItem') _items
ngAfterContentInit() {
setTimeout(() => {
this.grandchildElements.emit(this._items)
})
}
Set template reference #picker
, register to (grandchildElements)
event and set the $event
to picker.grandchildElements
<app-option-picker #picker [optionList]="[1, 2, 3]" (grandchildElements)="picker.grandchildElements = $event" popup-content>
Create Input on PopupComponent
to accept values from picker.grandchildElements
@Input('grandchildElements') grandchildElements: any
In app.component.html
accept picker.grandchildElements
to the input
<app-popup [grandchildElements]="picker.grandchildElements">
popup.component
set console.log
for open and close
open() {
if (this.grandchildElements) {
console.log(this.grandchildElements);
}
else {
console.log(this.childItems);
}
close() {
if (this.grandchildElements) {
console.log(this.grandchildElements);
}
else {
console.log(this.childItems);
}
popup.component
change your ContentChildren
back to listItem
@ContentChildren('listItem', { descendants: true }) childItems: Element;
popup.component.html
set header expression
<h3>Child Items: {{grandchildElements ? grandchildElements.length : childItems.length}}</h3>
Stackblitz
Upvotes: 4