I am trying to load different components in mat-sidenav from different routed components based on some action like button click?
I have created a singleton service 'SidenavService' to get reference to mat-sidenav using @ViewChild, this way I can control (like open, close, toggle etc.) sidenav from anywhere in the app.
I have also created a ng-container in mat-sidenav and stored its ViewConteinerRef in SidenavService, now in any component in component hierarchy, I can inject the SidenavService and I can use ViewConteinerRef to created embedded view.
stackblitz link https://stackblitz.com/edit/angular-material-sidenav-dynamic-content
Is there a better way to achieve this? loading different components in mat-sidenav from anywhere in the app.
Experimenting with your StackBlitz, I've come up with a solution using the portal component from the Angular CDK (Component DevKit).
What I've done is to modify the original SidenavService
code to allow a Portal
to be passed to a Subject
which will emit the Portal
to any components that have subscribed to the Subject
. The dynamic sidenav would then have a <ng-template>
portion inside of the sidenav with the cdkPortalOutlet
attribute bound to the Portal
Subject
:
<mat-sidenav #rightPanel position="end">
<ng-template [cdkPortalOutlet]="panelService.panelPortal | async"></ng-template>
</mat-sidenav>
export class AppComponent {
constructor(public panelService: PanelService) { }
}
However, be sure to import the PortalModule
from the @angular/cdk/portal
entry-point into your app's module:
import { PortalModule } from '@angular/cdk/portal';
// ...
@NgModule({
imports: [
// ...
PortalModule
],
// ...
})
By allowing the SidenavService
code to allow a Portal
reference, you can now pass either a template reference (TemplateRef<T>
) or a component (ComponentType<T>
) to the SidenavService
. This allows for the simplification of code where there would be no need to create a dedicated component for the sidenav's dynamic content.
However, a TemplatePortal
(which is a Portal
that represents an embedded template) requires that a ViewContainerRef
is specified for the second argument. In such a case, what I've done is to allow the user to specify a ViewContainerRef
to be passed to the service where it would be used for the TemplatePortal
.
Anyways, here's a portion of the service code. (Note: I've modified the service name to a better-sounding name: PanelService
)
import { from } from 'rxjs';
// ...
export class PanelService {
/** The panel. */
panel: MatSidenav;
private vcr: ViewContainerRef;
// Note: The Portal class requires that a generic is specified for the component/template type.
private panelPortal$ = new Subject<Portal<any>>();
get panelPortal() {
return from(this.panelPortal$); // Or this.panelPortal$.asObservable()
}
setViewContainerRef(vcr: ViewContainerRef) {
this.vcr = vcr;
}
setPanelPortal(portal: Portal<any>) {
this.panelPortal$.next(portal);
}
// Wrapper methods for MatSidenav go here.
// ...
}
Here's a StackBlitz for you to play around with.
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With