ngComponentOutlet: projected content defined in html

491 Views Asked by At

Do you know a way to project content to a component outlet with a content defined in the template it self ?

I've tried following that doesn't work :

@Component({
  template: `<ng-content></ng-content>`,
})
export class MyComp {}

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div *ngComponentOutlet="cmp">
      <div #ref>Ma projection</div>  <!-- I'd like to project this -->
    </div>
  `,
})
export class App {
  cmp = MyComp;
}

I know I can pass content to the *ngComponentOutlet but how could I pass something that is defined in the template, like <div #ref>Ma projection</div> here.

Stackblitz demo

2

There are 2 best solutions below

0
On BEST ANSWER

I came up with the following solution :

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
    <h1>Hello from {{name}}!</h1>
    <ng-template #ref><div>Ma projection</div></ng-template>  <!-- I'd like to project this -->
    <div *ngComponentOutlet="cmp"></div>
  `,
})
export class App {
  name = 'Angular';
  cmp = MyComp;
  projectables!: any[][];
  @ViewChild('ref', { static: true }) template!: TemplateRef<any>;

  vcr = inject(ViewContainerRef);

  ngOnInit() {
    this.projectables = [this.vcr.createEmbeddedView(this.template).rootNodes];
  }
}

I defined a template that is accessed by a @ViewChild.

When I have that TemplateRef in ngOnInit, I create a ViewRef that is my projectable content.

Et voilà.

1
On

You can use a ViewChild to reference the template content and then pass it to the component outlet using createEmbeddedView and attach.

import { Component, ViewChild, AfterViewInit, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  template: `<ng-content></ng-content>`,
})
export class MyComp {}

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div #container></div>
  `,
})
export class App implements AfterViewInit {
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private cfr: ComponentFactoryResolver) {}

  ngAfterViewInit() {
    const factory = this.cfr.resolveComponentFactory(MyComp);
    const componentRef = this.container.createComponent(factory);
    const templateRef = this.container.createEmbeddedView(this.ref.nativeElement);
    componentRef.instance.viewRef = templateRef;
    componentRef.changeDetectorRef.detectChanges();
    this.container.detach();
  }
}

ViewChild to get a reference to the ViewContainerRef of the div with the #container template variable. Then, in the ngAfterViewInit lifecycle hook, we create a component factory for MyComp and use it to create a new instance of the component. We also create an embedded view of the content defined by the #ref template variable using createEmbeddedView, and attach it to the component instance using instance.viewRef.

hope it's helps ..!