Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to do responsive components in Angular2

Tags:

I'm wading my way into Angular2. My objective is to create a responsive app that loads different components in response to different media-queries for device widths. My working example has a MatchMediaService:

import { Injectable } from '@angular/core';  @Injectable() export class MatchMediaService  {     constructor()     {      }      rules =     {         print: "print",         screen: "screen",         phone: '(max-width: 767px)',         tablet: '(min-width: 768px) and (max-width: 1024px)',         desktop: '(min-width: 1025px)',         portrait: '(orientation: portrait)',         landscape: '(orientation: landscape)',         retina: '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)'     };      Check = function (mq)     {         if (!mq)         {             return;         }          return window.matchMedia(mq).matches;     };  /**********************************************     METHODS FOR CHECKING TYPE     **********************************************/     IsPhone()     {         return window.matchMedia(this.rules.phone).matches;     };      IsTablet = function ()     {         return window.matchMedia(this.rules.tablet).matches;     };      IsDesktop = function ()     {         return window.matchMedia(this.rules.desktop).matches;     };      IsPortrait = function ()     {         return window.matchMedia(this.rules.portrait).matches;     };      IsLandscape = function ()     {         return window.matchMedia(this.rules.landscape).matches;     };      IsRetina = function ()     {         return window.matchMedia(this.rules.retina).matches;     };   /**********************************************     EVENT LISTENERS BY TYPE  **********************************************/         OnPhone(callBack)     {         if (typeof callBack === 'function')         {             var mql: MediaQueryList = window.matchMedia(this.rules.phone);              mql.addListener((mql: MediaQueryList) =>             {                 if (mql.matches)                 {                     callBack(mql);                 }             });         }     };      OnTablet(callBack)     {         if (typeof callBack === 'function')         {             var mql: MediaQueryList = window.matchMedia(this.rules.tablet);              mql.addListener((mql: MediaQueryList) =>             {                 if (mql.matches)                 {                     callBack(mql);                 }             });         }     };      OnDesktop(callBack)     {         if (typeof callBack === 'function')         {             var mql: MediaQueryList = window.matchMedia(this.rules.desktop);              mql.addListener((mql: MediaQueryList) =>             {                 if (mql.matches)                 {                     callBack(mql);                 }             });         }     };        OnPortrait(callBack)     {         if (typeof callBack === 'function')         {             var mql: MediaQueryList = window.matchMedia(this.rules.portrait);              mql.addListener((mql: MediaQueryList) =>             {                 if (mql.matches)                 {                     callBack(mql);                 }             });         }     };        OnLandscape(callBack)     {         if (typeof callBack === 'function')         {             var mql: MediaQueryList = window.matchMedia(this.rules.landscape);              mql.addListener((mql: MediaQueryList) =>             {                 if (mql.matches)                 {                     callBack(mql);                 }             });         }     }; } 

Then inside a 'parent' component (HomeComponent), I use MatchMediaService to determine which child component (HomeMobileComponent or HomeDesktopComponent) to load depending on what MatchMediaService returns as well as listener events that fire when browser resizes through different dimensions:

import { Component, OnInit, NgZone } from '@angular/core'; import { MatchMediaService } from '../shared/services/match-media.service'; import { HomeMobileComponent } from './home-mobile.component'; import { HomeDesktopComponent } from './home-desktop.component';  @Component({     moduleId: module.id,     selector: 'home.component',     templateUrl: 'home.component.html',     providers: [ MatchMediaService ],     directives: [ HomeMobileComponent, HomeDesktopComponent ] }) export class HomeComponent implements OnInit  {     IsMobile: Boolean = false;     IsDesktop: Boolean = false;      constructor(         private matchMediaService: MatchMediaService,         private zone: NgZone             )     {         //GET INITIAL VALUE BASED ON DEVICE WIDTHS AT TIME THE APP RENDERS         this.IsMobile = (this.matchMediaService.IsPhone() || this.matchMediaService.IsTablet());         this.IsDesktop = (this.matchMediaService.IsDesktop());          var that = this;           /*---------------------------------------------------         TAP INTO LISTENERS FOR WHEN DEVICE WIDTH CHANGES         ---------------------------------------------------*/          this.matchMediaService.OnPhone(             function (mediaQueryList: MediaQueryList)             {                 that.ShowMobile();             }         );          this.matchMediaService.OnTablet(             function (mediaQueryList: MediaQueryList)             {                 that.ShowMobile();             }         );          this.matchMediaService.OnDesktop(             function (mediaQueryList: MediaQueryList)             {                 that.ShowDesktop();             }         );     }      ngOnInit()     {      }      ShowMobile()     {         this.zone.run(() =>         { // Change the property within the zone, CD will run after             this.IsMobile = true;             this.IsDesktop = false;         });     }      ShowDesktop()     {         this.zone.run(() =>         { // Change the property within the zone, CD will run after             this.IsMobile = false;             this.IsDesktop = true;         });     }    } 
<home-mobile *ngIf="(IsMobile)"></home-mobile> <home-desktop *ngIf="(IsDesktop)"></home-desktop> 

This approach works. I can load the appropriate component in response to the device. It affords me the ability to customize a component (content, style, functionality, etc) to the device thus enabling the best user experience. This also affords me the ability to target different components for Mobile, Tablet, and Desktop (although I only am focusing on mobile and desktop in example).

Is there a better way to do this? The downside is that I'm forcing every component to be comprised of the parent component to determine via MatchMediaService which child component to load. Will this be scalable to work in a full blown production level app? I'm very interested in your feedback on a better approach, or if this approach is acceptable and scalable for a full blown production app. Thanks for you feedback.

like image 686
Tom Schreck Avatar asked Jul 18 '16 16:07

Tom Schreck


1 Answers

You can create a custom media-aware *ngIf or *ngSwitch structural directive to make it less repetitive.

https://angular.io/docs/ts/latest/guide/structural-directives.html

like image 184
Günter Zöchbauer Avatar answered Nov 09 '22 06:11

Günter Zöchbauer