You can't use output here as you can't get output from transcluded content, but there are a few ways to accomplish this...
this all relies on the TemplateRef
type and ViewChild
or ContentChildren
, all of which can be imported from angular core:
import { TemplateRef, ViewChild, ContentChildren } from '@angular/core'
Using Shared Service
You can actually use a service to pass templates around pretty effectively.
consider a simple service to pass templates via a Subject like so:
@Injectable()
export class TemplateService {
// allow undefined for resetting
private templateSource = new Subject<TemplateRef<any> | undefined>()
template$ = this.templateSource.asObservable()
setTemplate(template?: TemplateRef<any>) {
this.templateSource.next(template)
}
}
and a host component that provides and subscribes to it:
@Component({
selector: 'host',
templateUrl: './host.component.html',
// providing the service here makes sure the children of the component (and ONLY this children of this component) will have access to the same service instance
providers: [TemplateService]
})
export class HostComponent {
childTemplate?: TemplateRef<any>
constructor(private templateService: TemplateService) {
// receive templates from the service and assign
this.templateService.template$.subscribe(t => this.childTemplate = t)
}
}
with a template that defines an ngTemplateOutlet:
<div>
<h1>Host Content</h1>
<ng-container *ngTemplateOutlet="childTemplate"></ng-container>
</div>
<ng-content></ng-content>
*NOTE: you said you switch the content inside your component quite often, I'm asssuming you use ng-content
to do that, but this method would work equally well with a router outlet.
then a child component that sends the template through the service:
@Component({
selector: 'child1',
templateUrl: './child1.component.html',
})
export class Child1Component {
// access template with ViewChild
@ViewChild('childTemplate')
childTemplate?: TemplateRef<any>
constructor(private templateService: TemplateService) {
}
ngAfterViewInit() {
// set here in afterViewInit hook when ViewChild is available
this.templateService.setTemplate(this.childTemplate)
}
ngOnDestroy() {
// need to clean up
this.templateService.setTemplate()
}
childFunc() {
console.log('child func called')
}
}
with template that defines the template to pass with ng-template:
<h2>Child 1 Content</h2>
<ng-template #childTemplate><button (click)="childFunc()">Run Child Func</button></ng-template>
use it like so:
<host>
<child1></child1>
</host>
this will effectively pass the child template from the child to the host, and the functions defined in that template will still run in the child. The only real gotcha here is that you need to make sure you clean up after yourself in the onDestroy hook. If a given child doesn't have a special template, that's fine too. Just nothing will be in the template outlet. If the child components need to be available in other contexts, simply mark the templateService as optional and only set the template if it's provided.
blitz (expanded with header / footer): https://stackblitz.com/edit/angular-7-master-v81afu
Using Content Children
An alternative is to use ContentChildren, if you defined your host like this (same template and children as before but without the service stuff):
@Component({
selector: 'host',
templateUrl: './host.component.html',
})
export class HostComponent {
childTemplate?: TemplateRef<any>
// use content children to access projected content
@ContentChildren('child')
children: QueryList<any>
ngAfterContentInit() {
// set from initial child
this.childTemplate = (this.children.first || {}).childTemplate
// listen for changes and reset
this.children.changes.subscribe(child => {
this.childTemplate = (child.first || {}).childTemplate
})
}
}
then you'd just need to mark the possible children when you use your host:
<host>
<child1 #child></child1>
</host>
Here the gotcha is you need to make sure you have at most one marked content child available at a time. Otherwise it'll just take the first one (or you could define whatever logic you want to find the template you want). The null checks make a child without a template perfectly acceptable.
blitz (expanded with header / footer): https://stackblitz.com/edit/angular-7-master-8gc4yb
IMPORTANT:
In either case you MUST clean up. holding a reference to the child's template in the host after the child is destroyed creates the potential for a memory leak or buggy behavior as the children won't be able to be destroyed or garbage collected correctly.
The service method is a bit more general / flexible, such as you can pass template from children of children easily, or you can switch the template from the child quite easily, or you can do it with a router outlet instead of transclusion. But ContentChildren is a bit more straight forward, though it only works with ng-content. Both are equally valid but depend on your use case.