Material2: Show/Hide md-sidenav depending on media
Asked Answered
D

5

5

i would like to open the md-sidenav on large screen and close it on mobile device. What would be the right way to do it inside my application?

Is there any possibility to query the media inside angular2 component?

Duro answered 29/10, 2016 at 16:10 Comment(4)
Have you managed to figure this out? I have just ran into this very need with my app...Veronicaveronika
@Veronicaveronika just provided you a possible solutionDuro
Actually, I have figured it out yesterday for myself... I can share my solution with you if you're interested :-)Veronicaveronika
@Veronicaveronika sure, let me see!Duro
D
8

Inside of component class i've defined a reference to the sidenav and listening to the window resize events. Depending on the window.innerWith you can build your logic.

  @ViewChild('sidenav') sidenav: MdSidenav

  @HostListener('window:resize', ['$event'])
  onResize(event) {
    this.configureSideNav()
  }

  configureSideNav() {
    this.smallScreen = window.innerWidth < 501 ? true : false
    if (!this.smallScreen) {
      this.sidenav.mode = "side"
      this.sidenav.opened = true
    } else {
      this.sidenav.mode = 'over'
      this.sidenav.opened = false
    }
  }
Duro answered 28/11, 2016 at 21:58 Comment(0)
V
5

i've just found a little bit better approach to listen to screen changes in Angular: https://github.com/angular/flex-layout/wiki/ObservableMedia

it's a Angular Flex Layout module that provides the mediaQuery activations notifications as observable. so there is no need to listen to create a resize listener manually.

besides, MediaChange Class provides handy aliases (lg, md, xs etc.) that can be used to make decisions about screen size-specific expressions.

check out examples on their wiki-page.

Varicotomy answered 6/6, 2017 at 17:36 Comment(0)
V
3

We have a dashboard component which is the layout container for the entire app.

dashboard.html:

<md-sidenav-layout class="sidenav-layout">

  <bv-toolbar (toggleSidenav)="start.toggle()" [screenWidth]="screenWidth"></bv-toolbar>
  <md-sidenav #start [opened]="screenWidth > 1000" [mode]="(screenWidth > 1000) ? 'side' : 'start'">
    <bv-sidenav-content  [user]="user$ | async" [screenWidth]="screenWidth" (navClose)="start.toggle()"></bv-sidenav-content>
  </md-sidenav>

  <div class="sidenav-content"></div>
</md-sidenav-layout>

Note how we send the screenWidth value from the dashboard to the toolbar (and sidenav) components via the template using [screenWidth]="screenWidth".

dashboard.ts:

...
export class DashboardComponent {
  screenWidth: number;
...
constructor (private cdr: ChangeDetectorRef, private store: Store<fromRoot.State> ) {

  var that = this;
  // set screenWidth on page load
  that.screenWidth = window.innerWidth;
  window.onresize = () => {
    // set screenWidth on screen size change
    that.screenWidth = window.innerWidth;
    that.cdr.detectChanges();
  }
}

Now that the screenWidth is set on our dashboard, and sent via the template to the other components, we need to receive it in the child-component's script using @Input() and then it will be available in the child component's template as well.

toolbar.ts:

...
export class ToolbarComponent {
  @Output() toggleSidenav = new EventEmitter();
  @Input() screenWidth : number;
}

This will allow the toolbar's template to have access to the screenWidth variable defined earlier in the dashboard, and to get updates every time it is being changed (so you can set the logic once in the dashboard and inherit that value everywhere across the app.

toolbar.html:

<md-toolbar color="primary">
  <button md-icon-button
          (click)="toggleSidenav.emit()"
          [ngClass]="{'h-hide': screenWidth > 1000}">
      <md-icon>menu</md-icon>
  </button>
</md-toolbar>
Veronicaveronika answered 4/12, 2016 at 10:41 Comment(2)
ok, you are also reacting to the window:resize event and using window.innerWidth to solve the problem. this is the key for the solutionDuro
I don't know if this is the best practice, but this is what we have ended up with. Still up for more suggestions if anyone will see this thread in the future...Veronicaveronika
C
1

I found the solution from another post: rxjs/behaviorsubject.

You can get the value of the width from BehaviorSubject.window. And I'm using it instead to switch the sidenav mode between "side" and "over".

windows.service.ts:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class WindowService {
    public window = new BehaviorSubject(null);

    public width: Observable<number>;

    constructor() {
        let windowSize = new BehaviorSubject(getWindowSize());

        this.width = (windowSize.pluck('width') as Observable<number>).distinctUntilChanged();

        Observable.fromEvent(window, 'resize')
            .map(getWindowSize)
            .subscribe(windowSize);
    }


}

function getWindowSize() {
    return {
        width: window.innerWidth
    };
}

component.ts:

ngOnInit() {
    // Change sideNav mode between over and side depending on size of window
    this.windowService.width.subscribe((width) => {
      if (width) {
        console.log(width);
        if (width < 600) {
          this.sideNavMode = "over"
        } else {
          this.sideNavMode = "side"
        }
      }
    });
}
Cm answered 13/3, 2017 at 23:12 Comment(0)
U
1

I solved this exact same issue today in my workplace. I have done it basically using the same approach that @AndyGrey pointed out. I thought the code sample might help someone.

sidenav-layout.component.ts

    import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
    import { ObservableMedia, MediaChange } from '@angular/flex-layout';
    import { MdSidenav } from '@angular/material';
    import { Subscription } from 'rxjs/Subscription';

    @Component({
      selector: 'sidenav-layout',
      templateUrl: './sidenav-layout.component.html',
      styleUrls: [ './sidenav-layout.component.scss' ]
    })

    export class SidenavLayoutComponent implements OnInit, OnDestroy {

      @ViewChild('sidenav') sidenav: MdSidenav;

      private _mediaSubscription: Subscription;
      sidenavOpen = true;
      isMobile = false;

      constructor(private media: ObservableMedia) {}

      ngOnInit() {
        this._mediaSubscription = this.media.asObservable().subscribe((change: 
     MediaChange) => {
      this.isMobile = (change.mqAlias === 'xs') || (change.mqAlias === 'sm');
      this.sidenavOpen = !this.isMobile;
    });
  }

  onLinkClick() {
    if (this.isMobile) {
      this.sidenav.toggle();
    }
  }

  ngOnDestroy() {
    this._mediaSubscription.unsubscribe();
  }
}

sidenav-layout.component.html

 <md-sidenav #sidenav mode="side" opened={{sidenavOpen}}>

The onLinkClick() method should be added to all your list-items. So that whenever a link is clicked in the mobile view the sidenav will be toggled(hidden).

Well at the end do not forget to unsubscribe the mediaSubscription Observable :)

Unpeople answered 22/6, 2017 at 0:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.