nipuna-g
nipuna-g

Reputation: 6652

@ContentChildren not picking up items inside custom component

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

Answers (3)

cyberblast
cyberblast

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

Kevin Beal
Kevin Beal

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

Marshal
Marshal

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.


enter image description here


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

https://stackblitz.com/edit/angular-popup-child-selection-issue-bjhjds?embed=1&file=src/app/option-picker/option-picker.component.ts

Upvotes: 4

Related Questions