How to impliment a sidenav Navigation drawer with a (Mini variant)

I have wrestled with this quite a bit. The solution is much simpler then you might think...almost everything in angular can be animated and we can solve this problem with a few lines of very simple animation code...

in a file called sidenav.animations.ts You will create an animation to animate the width of the <mat-sidenav> between 200px and 60px

You will create a second animation to animate the <mat-sidenav-content> between having a margin-left of 201px and 61px.

./sidenav.animations.ts

 import {
  animate,
  state,
  style,
  transition,
  trigger,
} from '@angular/animations';

/*
 * animation: sideNaveAnimation
 * trigger: 'openClose'
 *
 * comments: sets the width of an element to 200px when 'open' and to 60px
 *   when closed.  Animates in between these two states over '0.3s'
 */

export const sideNavAnimation = trigger('openCloseSidenav', [
    // ...
    state('open', style({
      width: '200px',
    })),
    state('closed', style({
      width: '60px',
    })),
    transition('open <=> closed', [
      animate('0.3s')
    ]),
  ]);

/*
 * animation: sideNavContainerAnimation
 * trigger: 'openCloseSidenavContent'
 *
 * comments: Sets the margin-left to 201px when "open" and 61px when "closed".
 */

export const sideNavContainerAnimation = trigger('openCloseSidenavContent', [
    state('open', style({
      'margin-left': '201px',
    })),
    state('closed', style({
      'margin-left': '61px',
    })),
    transition('open <=> closed', [
      animate('0.3s')
    ]),
  ]);

in your app.component.ts...

You will import the two animations and use them in the animations array which will allow you to use the triggers as defined in sidenav.animations.ts ('openCloseSidenav' and 'openCloseSidenavContent') within your app.component.html

You will create a boolean (isOpen) to keep track of which state the sidenav is in

You will create a function called toggle() that will switch the variable isOpen between true and false. This function will be called from the press of a button in app.component.html

./app.component.ts Don't forget to import your MatSidenavModule, MatButtonsModule, MatIconModule, MatListModule, and BrowserAnimationsModule into your app.module.ts as well or you won't be able to use <mat-sidenav> or any of that anywhere in your app.component.html

import { Component } from '@angular/core';

//...

import { sideNavAnimation, sideNavContainerAnimation } from './sidenav.animations';

//...


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  animations: [sideNavAnimation, sideNavContainerAnimation]
})

export class AppComponent {

  isOpen = true;

  toggle() {
     this.isOpen = !this.isOpen;
   }

//...

}

and in your app.component.html... You will call toggle() with a button placed somewhere in the sidenav.

You will place the animation trigger: 'openCloseSidenav' on the <mat-sidenav> with an expression that will choose which of the two defined states ('open' or 'closed' as defined in sidenav.animations.ts) the element should be in and animate between the states based on the state of the variable isOpen.

You will place the animation trigger: 'openCloseSidenavContent' on the <mat-sidenav-content> with the same expression as above.

./app.component.html

<mat-sidenav-container>

  <mat-sidenav [@openCloseSidenav]="isOpen ? 'open' : 'closed'" mode="side" opened role="navigation">
    <mat-nav-list>
      <!-- Place nav links here -->

      <button type="button" aria-label="Toggle nav" mat-icon-button (click)="toggle()">
      <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
      </button>

    </mat-nav-list>
  </mat-sidenav>

  <mat-sidenav-content [@openCloseSidenavContent]="isOpen ? 'open' : 'closed'">
    <router-outlet></router-outlet>
  </mat-sidenav-content>

</mat-sidenav-container>

You will have to fill in the rest yourself, my <mat-sidenav-list> has stuff as shown below in it.

...I am using the variable isOpen to decide if the text next to the icons should be displayed..I will soon either add in animations to the <mat-chip-list> tags to make those fade out when the toggle() is called and isOpen changes or I will shorten the animation time down to 0.0s so that it snaps instantly between the two states...

...this will however work as is even without the *ngIf="isOpen" in the tag...

    <a mat-list-item (click)="signOut()">
        <span  class="app-nav-list-icon">
          <mat-icon matListIcon class="app-nav-list-icon">
            <fa-icon icon="sign-out-alt"style="color: #808DE1;"></fa-icon>
          </mat-icon>
        </span>
        <mat-chip-list *ngIf="isOpen" style="padding-left: .5em;">
          <mat-chip>Logout</mat-chip>
        </mat-chip-list>
      </a>

...big take away here is that simple animations can be used to control the behavior and look of every aspect of your app...

visit https://angular.io/guide/animations for more details on exactly how this works...


Using typescript code you can update the width of sidebar like this :

toggleSideBar(){
    // console.log('toggle called');
    // this.sb.toggle();
    if(this.widthSideBar == this.widthSideBarExpanded){
      this.widthSideBar = this.widthSideBarCollapsed;
    }
    else{
      this.widthSideBar = this.widthSideBarExpanded;
    }
    // snav.toggle();
  }

and the corresponding html file will look like

..
<mat-sidenav [style.width.px]="widthSideBar">..</mat-sidenav>
..
<mat-sidenav-content [style.marginLeft.px]="widthSideBar">..</mat-sidenav-content>
..

Here I have set the width and margin-left of the content to adjust dynamically.


I have passed this problem with that solution.

Good luck.

Also, I have made a working example in Stackblitz

enter image description here

home.component.html

    <div>
    
      <mat-sidenav-container>
    
        <mat-sidenav #adminNavMenu mode="side" opened="true" style="min-width:60px; background: #F3F3F3;" class="shadow_right" autosize>
    
          <!-- MENU LEFT -->
          <app-admin-menu-left></app-admin-menu-left>
    
        </mat-sidenav>
    
        <!-- ================================================================= -->
        <!-- ************************* MAIN CONTAINER ************************ -->
        <!-- ================================================================= -->
        <mat-sidenav-content [@onSideNavChange]="sideNavState">
          <div class="main_container" fxLayout="column" fxLayoutGap="0px" style="height:100vh;">
    
            <!-- =============================================================== -->
            <!-- Your main content -->
            <!-- =============================================================== -->
    
          </div>
        </mat-sidenav-content>
    
      </mat-sidenav-container>
    
    
    </div>

home.component.ts


    import { Component, OnInit } from '@angular/core';
    import { MatSidenav } from '@angular/material';
    import { trigger, state, style, transition, animate } from '@angular/animations';
    
    import { MediatorService } from '@app-services/mediator/mediator.service';
    
    @Component({
      selector: 'app-admin-analytics',
      templateUrl: './admin-analytics.component.html',
      styleUrls: ['./admin-analytics.component.css'],
      animations: [
        trigger('onSideNavChange', [
          state('close',
            style({
              'margin-left': '60px'
            })
          ),
          state('open',
            style({
              'margin-left': '250px'
            })
          ),
          transition('close => open', animate('250ms ease-in')),
          transition('open => close', animate('250ms ease-in')),
        ]),
    
        trigger('onPageReady', [
          state('inactive',
            style({
              opacity: 0.4
            })
          ),
          state('active',
            style({
              opacity: 1
            })
          ),
          transition('inactive => active', animate('250ms ease-in')),
          transition('active => inactive', animate('250ms ease-in')),
        ])
      ]
    })
    export class HomeComponent implements OnInit {
    
    
      /**
       * Get the sidenav state.
       */
      sideNavState: string = this.mediator.getSideNavState;
    
 
     
      constructor(
        private mediator: MediatorService,
      ) { }
    
      ngOnInit() {
    
    
        // Subscribe on changes important.
        this.mediator.sideNavListener.subscribe( state => {
          this.sideNavState = state;
        });
    
      }
    
    
    }

mediator.service.ts


    import { Injectable } from '@angular/core';
    import { Subject } from 'rxjs/Subject';
    
    
    @Injectable()
    export class MediatorService {
    
      APP_VERSION: String = 'v8.3.1.36';
    
      // default value.
      // this variable track the value between sessions.
      private _sideState: any = 'open';
    
      /** This is the mini variant solution with animations trick. */
      sideNavListener: any = new Subject();
    
      get sideNavState() {
        return this._sideState;
      }
    
      setSidenavState(state) {
        this._sideState = state;
      }
    
    
      constructor() {
    
        this.sideNavListener.subscribe( state => {
          this.setSidenavState(state);
        });
      
    
      }
    
    
    }

menu-left.component.html


    <div class="sidenav_menu_left" 
        [@onSideNavChange]="sideNavState" 
        style="width:100%; height: 100vh;" 
        fxLayout="column" 
        [style.overflow]="overflowState">
    
        <p>Sidenav content left</p>
    
        <!-- this can toggle the sidenav -->
        <div fxFlex="100" (click)="toggleSideNav();" class="hoverble"></div>
    
    </div>

menu-left.component.ts


    import { Component, OnInit, Input } from '@angular/core';
    import { MatSidenav } from '@angular/material';
    import {trigger, state, style, transition, animate, keyframes, query, group} from '@angular/animations';
    
    
    // Mediator: the main service, later this service is gonna have more generic use.
    import { MediatorService } from '@app-services/mediator/mediator.service';
    import { delay } from 'q';
    
    
    @Component({
      selector: 'app-admin-menu-left',
      templateUrl: './admin-menu-left.component.html',
      styleUrls: ['./admin-menu-left.component.css'],
      animations: [
    
        // animate sidenave
        trigger('onSideNavChange', [
          state('close',
            style({
              width: '60px'
            })
          ),
          state('open',
            style({
              width: '250px'
            })
          ),
          transition('close => open', animate('250ms ease-in')),
          transition('open => close', animate('250ms ease-in')),
    
        ])
    
      ]
    })
    export class MenuLeftComponent implements OnInit {
    
    
      /**
       * Get the sidenav state,
       */
      sideNavState: string = this.mediator.sideNavState;
    
    
      overflowState: any = 'auto';
    
      constructor(
        private mediator: MediatorService
      ) {
    
      }
    
      ngOnInit() {
    
        this.mediator.sideNavListener.subscribe( state => {
          this.sideNavState = state;
        });
    
      }
    
      /**
       * On animation done.
       * @param x
       */
      animationEvent(x) {
        this.overflowState = 'auto';
      }
    
      /**
       * Toggle the sidenave state.
       *
       * Hides entire sidenav onclose.
       */
      setSideNavState() {
        this.mediator.toggle().then( snap => {
          console.log(snap);
        });
      }
    
    
      /**
       * Toggle, Open or close the sidenav.
       *
       * Set the sidenave state on mediator.
       */
      toggleSideNav() {
    
        switch (this.sideNavState) {
    
          case 'close':
    
            this.sideNavState = 'open';
            this.mediator.setSideNavState(this.sideNavState);
    
            setTimeout( () => {{
              this.sideNavText      = this.sideNavText === 'open' ? 'close' : 'open';
              this.sideNavIcon      = this.sideNavIcon === 'open' ? 'close' : 'open';
              this.sideNavCopyRight = this.sideNavCopyRight === 'open' ? 'close' : 'open';
            }}, 200);
          break;
    
          case 'open':
            this.sideNavText      = this.sideNavText === 'open' ? 'close' : 'open';
            this.sideNavIcon      = this.sideNavIcon === 'open' ? 'close' : 'open';
            this.sideNavCopyRight = this.sideNavCopyRight === 'open' ? 'close' : 'open';
    
            setTimeout( () => {{
              this.sideNavState = this.sideNavState === 'open' ? 'close' : 'open';
              this.mediator.setSideNavState(this.sideNavState);
            }}, 200);
          break;
    
          default:
            console.log('#6644');
            break;
        }
    
        this.overflowState = 'hidden';
      }
    
    }


You have to put in the header the property "autosize"

 <mat-sidenav-container autosize >

with this property the content adjust to the menu's width