Search code examples
angularangular-materialangular-directiveangular-components

Creating/Fetching a TemplateRef from within a directive


I’m trying to create a (structural) directive that inserts a TemplateRef, but where the TemplateRef is defined "somewhere else".

Context

I sometimes want to insert arbitrary content into an existing element, but for DOM reasons, it’s important that it not be a component (though an attribute-like component is fine).

Example:

<table>
  <tr my-row-component></tr>
</table>
@Component({
  selector: 'tr[my-row-component]'
  template: `<td>...</td><td>...</td><td>...</td>...`
})

Now from here, I’d want to do the same thing, but insert 2 rows into my table. So I was hoping to do something like:

<table>
  <ng-template myTwoRowsDirective></ng-template>
</table>

The issue is that:

  • I have a structural directive, so that I can insert the code that I want
  • I need a component, so that I can write the html I want to insert into the directive.

Question

How could I get a TemplateRef inside a Structural directive, but that is not passed in by the caller of the directive?

@Directive({selector: '[myTwoRowsDirective]'})
export class MyTwoRowsDirective {
  constructor(
      viewContainerRef: ViewContainerRef) {
    const templateRef = ???; // Reference to template defined elswhere
    viewContainerRef.createEmbeddedView(templateRef, this.context);
  }
}

Solution

  • Don't know if this is a recommended practice, but this seems to work (haven't tested it on your use case though):

    @Component({
      template: `
        <ng-template #helloRef>
          <h1>hello</h1>
        </ng-template>
      `
    })
    export class TemplatesComponent {
      @ViewChild('helloRef', { static: true }) public helloRef: TemplateRef<any>;
    }
    
    @Directive({
      selector: 'whatever-component'
    })
    export class CustomizeWhateverDirective implements AfterViewInit {
      private static componentRef: ComponentRef<TemplatesComponent>;
    
      constructor(
        @Self() private whatever: WhateverComponent,
        private resolver: ComponentFactoryResolver,
        private _vcr: ViewContainerRef
      ) {}
    
      ngAfterViewInit(): void {
        const componentRef = this.getComponentRef();
    
        const helloRef = componentRef.instance.helloRef;
        this.whatever.helloTemplate = helloRef;
      }
    
      private getComponentRef() {
        if (!CustomizeWhateverDirective.componentRef) {
          const factory = this.resolver.resolveComponentFactory(TemplatesComponent);
          CustomizeWhateverDirective.componentRef = this._vcr.createComponent(factory);
        }
    
        return CustomizeWhateverDirective.componentRef;
      }
    }
    

    This code sets the helloTemplate attribute of all Whatever components in my project.

    So the trick is to have a component with the templateRef (TemplatesComponent in the example), and create that component (through viewContainerRef.createComponent) and access the templateRef.