angularng-content

Angular Component Dynamic Compile - ng-content with select not working


I have some components that are compiled dynamically using the resolveComponentFactory.

I do this like so....

const embeddedComponentElements = this.hostElement.querySelectorAll( selector );

const element = embeddedComponentElements[ 0 ];

// convert NodeList into an array, since Angular doesn't like having a NodeList passed
const projectableNodes = [
  Array.prototype.slice.call(element.childNodes),
];

const factory = componentFactoryResolver.resolveComponentFactory( Component );

const embeddedComponent = factory.create(
 this.injector,
 projectableNodes,
 element,
);

But I have a problem that i'm struggling to solve....

This works as expected

<div>
 <ng-content></ng-content>
</div>

This does NOT work as expected:

<div>
 <!-- I get all the content here -->
 <ng-content select="h1"></ng-content>
</div>
<div>
 <!-- I don't get any content here -->
 <ng-content></ng-content>
</div>

It seems that the ng-content select attribute does not work with dynamically compiled content.

Any ideas what I'm doing wrong or is this a bug?

I'm using Angular 8 if that helps.

Update: I've reproduced this issue in stackBlitz here: https://stackblitz.com/edit/angular-ivy-givxx6


Solution

  • Thanks Poul. Your response helped me a lot. I wrote the below and my unit tests confirm it all now works as expected.

    const projectableNodes = this.getNgContent( element, factory.ngContentSelectors );
    
    
    private getNgContent( element: Element, ngSelectors: string[] ) {
    
        // select the content
        const elements = ngSelectors
          .map ( s => {
            if ( s !== '*' ) {
              const els = element.querySelectorAll( s );
              els.forEach( e => e.remove() );
              return Array.from( els );
            }
            return s;
          });
    
        // where content has not been selected return remaining
        return elements.map( s => {
          if ( s === '*' ) {
            return Array.from( element.childNodes )
          }
          return s;
        });
      }