Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

change mat-sidenav content dynamically from any component

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.

like image 368
Mujahid Avatar asked Jun 26 '19 12:06

Mujahid


1 Answers

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!

like image 158
Edric Avatar answered Oct 21 '22 19:10

Edric