Expanding on the answer by @peter554 for reasons of explanation and portability. This will let you use a template across components.
To use:
'app.module.ts'
import {NgModule} from '@angular/core';
import {
IdcPortalDirective, IdcTemplatePortalDirective,
PortalService
} from './idc-template-portal/idc-template-portal.component';
@NgModule({
declarations: [
IdcPortalDirective,
IdcTemplatePortalDirective
],
imports: [],
exports: [],
providers: [
PortalService
],
bootstrap: [AppComponent]
})
export class AppModule {}
'./idc-template-portal/idc-template-portal.component.ts'
import {
AfterViewInit,
Directive,
Injectable,
Input,
OnInit, Output,
TemplateRef,
ViewContainerRef
} from '@angular/core';
/*** Input Template ***/
/*** <ng-template idcPortal [outlet]="'outletname'">Template Contents</ng-template> ***/
@Directive({
selector: '[idcPortal]'
})
export class IdcPortalDirective implements OnInit {
@Input() outlet: string;
@Output() inlet: string = this.outlet;
constructor(private portalService: PortalService, public templateRef: TemplateRef<any>) {}
ngOnInit():void {
this.portalService.registerInlet(this);
}
}
/*** Output Container ***/
/*** <ng-container [idcPortalOutlet]="'outletname'"></ng-container> ***/
@Directive({
selector: '[idcPortalOutlet]'
})
export class IdcTemplatePortalDirective implements OnInit, AfterViewInit {
@Input() appPortalOutlet: string;
@Output() outlet: string = this.appPortalOutlet;
constructor(private portalService: PortalService, public viewContainerRef: ViewContainerRef) {}
ngOnInit():void {
this.portalService.registerOutlet(this);
}
ngAfterViewInit() {
this.portalService.initializePortal(this.appPortalOutlet);
}
}
@Injectable({
providedIn: 'root'
})
export class PortalService {
outlets = new Map<string, IdcTemplatePortalDirective>();
inlets = new Map<string, IdcPortalDirective>();
registerOutlet(outlet: IdcTemplatePortalDirective) {
this.outlets[outlet.outlet] = outlet;
}
registerInlet(inlet: IdcPortalDirective) {
this.inlets[inlet.inlet] = inlet;
}
initializePortal(portal:string) {
const inlet: IdcPortalDirective = this.inlets[portal];
const outlet: IdcTemplatePortalDirective = this.outlets[portal];
outlet.viewContainerRef.clear();
outlet.viewContainerRef.createEmbeddedView(inlet.templateRef);
}
}
He,@peter554, mentions reinventing the wheel in regards to the Angular CDK portals package. However, I find his/this implementation to make more sense in the way it's used in the application flow and the ease in which a template can be ported from component to another component that contains the portal outlet (allowing component to component->portal template communication. For example within a component template implementing the Angular Material MatBottomSheet (idcBottomSheet)).
The 'input' element:
<!--
/*
For example, perhaps you have a mobile view
where a template is hidden (via css) and ported
over to a MatBottomSheet component template to be
popped up when requested (by button click).
*/
-->
<button #bottomsheetButton (click)="openBottomSheet(Notes)" mat-button>
<mat-icon>notes</mat-icon>
</button>
<!--/* hidden in mobile view mode. */-->
<ng-content *ngTemplateOutlet="Notes"></ng-content>
<ng-template #Notes idcPortal [outlet]="'idcBottomSheet'"><!--/* template to port */-->
<form>
<mat-form-field class="w-100 h-100">
<mat-label>A place for your thoughts:</mat-label>
<textarea matInput
cdkTextareaAutosize
#autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="10"
cdkAutosizeMaxRows="10"
placeholder="Angular. It makes me feel...">
</textarea>
</mat-form-field>
</form>
</ng-template>
The 'output' element (inside your MatBottomSheet component template):
<ng-container [idcPortalOutlet]="'appIdcBottomSheet'"></ng-container>