SBKDeveloper
SBKDeveloper

Reputation: 693

Injecting template content from parent into child

I have a 3rd party library component that I am trying to customize by adding some additional markup at a certain point. I am using a structural directive to do this and currently I am programmatically adding and styling nodes using the renderer like so:

  const headerDiv = this.newDiv();
   el = this.nativeElement.querySelector('div.ui-dropdown-items-wrapper');
   this.renderer.appendChild(el, this.renderer.createText('sometext'));


  private newDiv(): Element {
   return this.renderer.createElement('div');
 }

The markup is like so:

<div #containerDiv>
  <child-component *myStructuralDirective>
  </child-component>
</div>

is there a way to define an <ng-template> directly in the parent component's markup and inject that at a certain point using the renderer? Something like this:

<div #containerDiv>
  <child-component *myStructuralDirective>
  </child-component>
</div>
<ng-template #footer>
   <p>Some other markup</p>
</ng-template>

and then inject the content of #footer at some point of my choosing into the child-component. Note - I have no access to the child since it's a compiled 3rd party lib.

The gist of this is that I am trying to see if there is a better way to define the markup as a template variable that I can use in my structural directive, get access to that template and inject it at a certain point in the child component.

Edit - I am looking at ViewContainerRef.insert, unfortunately this can insert templates only based on location of other ViewRefs. Since I cannot change the markup of the 3rd party component I can define a ng-container to mark the point of insertion and have to only use CSS selectors. Not sure if there is a way to insert a template based on a element location. I know these are 2 discrete concepts (Angular View abstractions vs direct DOM insertion) so i'm not optimistic this can be done!

Upvotes: 6

Views: 8334

Answers (1)

KShewengger
KShewengger

Reputation: 8269

Yes, you can inject ng-template to wherever you want it to be placed either with these following methods:

1.) Using ng-container

 <div #containerDiv>
  <child-component *myStructuralDirective></child-component>

  // This will show your template
  <ng-container [ngTemplateOutlet]="footer"></ng-container>  

</div>

<ng-template #footer>
   <p>Some other markup</p>
</ng-template>

2.) Or Inject it using a structural directive with a ViewContainerRef & CreateEmbeddedView

a.)Supply the footer template on your childComponent. myStructuralDirective
is now both a directive and an [] input that asks for a value.

<child-component [myStructuralDirective]="footer"></child-component>

b.) Use default ViewContainerRef declared on the constructor which pertains 
to your current view/screen which the template the template is attached example for this - it is attached on ChildComponentView

@Directive({
    selector: 'myStructuralDirective'
})
export class MyStructuralDirective implements AfterViewInit {

 @Input() myStructuralDirective: TemplateRef<any>;

 constructor(private container: ViewContainerRef) {}

 ngAfterViewInit() {
     this.container.createEmbeddedView(this.myStructuralDirective);
  }

}

3.) Or Supply an @Input() on your ChildComponent passing the footer template value

 a.)  <child-component *myStructuralDirective
                       [footerTemplate]="footer"></child-component>

 b.) On the ChildComponent Class, declare its @Input() property

 @Component({
     selector: 'child-component'
  })
  export class ChildComponent {

    @Input() footerTemplate: TemplateRef<any>;

  }

 c.) On your ChildComponent Template, supply an ng-container to wherever you want it to be placed.

 <h1>Header</h1>
 <h1>Content</h1>

 <ng-container [ngTemplateOutlet]="footerTemplate"></ng-container>

4.) Or you can supply a container on your ChildComponent

a.) ChildComponent Sample Template

<p>Header</p> 
<p>Content</p>

<div #container></div>

b.) Declare ViewContainerRef and TemplateRef on your ChildComponent Class with the @Input() that requires the footer template value from the component

@Component({...})
export class ChildComponent implements AfterViewInit {

  //From your template <div #container></div> 
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  @Input() footerTemplate: TemplateRef<any>;


  ngAfterViewInit() {
     this.container.createEmbeddedView(this.footerTemplate);
}

}

Your ng-template will now appear on your desired location.

Upvotes: 9

Related Questions