как сделать адаптивные компоненты в Angular2

Я пробираюсь в Angular2. Моя цель — создать отзывчивое приложение, которое загружает разные компоненты в ответ на разные медиа-запросы для ширины устройства. В моем рабочем примере есть 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);
                }
            });
        }
    };
}

Затем внутри «родительского» компонента (HomeComponent) я использую MatchMediaService, чтобы определить, какой дочерний компонент (HomeMobileComponent или HomeDesktopComponent) загружать в зависимости от того, что возвращает MatchMediaService, а также событий прослушивателя, которые срабатывают при изменении размера браузера в разных измерениях:

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>

Этот подход работает. Я могу загрузить соответствующий компонент в ответ на устройство. Это дает мне возможность настраивать компонент (содержание, стиль, функциональность и т. д.) для устройства, что обеспечивает наилучшее взаимодействие с пользователем. Это также дает мне возможность ориентироваться на различные компоненты для мобильных устройств, планшетов и настольных компьютеров (хотя в качестве примера я сосредоточился только на мобильных устройствах и настольных компьютерах).

Есть лучший способ сделать это? Недостатком является то, что я заставляю каждый компонент состоять из родительского компонента, чтобы через MatchMediaService определить, какой дочерний компонент загрузить. Будет ли это масштабироваться для работы в полнофункциональном приложении производственного уровня? Меня очень интересуют ваши отзывы о лучшем подходе или о том, является ли этот подход приемлемым и масштабируемым для полноценного производственного приложения. Спасибо за ваш отзыв.


person Tom Schreck    schedule 18.07.2016    source источник


Ответы (2)


Вы можете создать пользовательскую структурную директиву *ngIf или *ngSwitch с поддержкой мультимедиа, чтобы сделать ее менее повторяющейся.

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

person Günter Zöchbauer    schedule 18.07.2016
comment
Том, этот дизайн заставит настольные и мобильные компоненты жить в одной сборке, что означает большой пакет с неиспользуемым кодом. Вы не сможете встряхнуть его так, как он использовался, но не использовался на самом деле. Например, <home-mobile> никогда не запустится на рабочем столе, верно? - person Shlomi Assaf; 18.07.2016
comment
Спасибо за ответ. Я не смог найти ничего конкретного о структурных директивах, связанных со средствами массовой информации, в предоставленном вами URL-адресе. Я использую структурную директиву ngIf для загрузки дочернего компонента и использую MatchMediaService для аспекта мультимедиа, предоставляя точку принятия решения для ngIf. Что мне не хватает? - person Tom Schreck; 18.07.2016
comment
У вас есть идея получше? Кажется, команда Angular хочет предоставить что-то нестандартное, но не может придумать хороший подход. @View() должен был помочь здесь, но от него отказались, потому что это не сработало. - person Günter Zöchbauer; 18.07.2016
comment
@TomShreck Я имел в виду, что вы можете создать собственный *ngIf или *ngSwitch, например *meadiaSwitch, который добавляет часть, соответствующую текущей ширине (или чему-то еще). Ссылка предназначена только для того, чтобы показать, как создавать структурные директивы. - person Günter Zöchbauer; 18.07.2016
comment
Ленивая загрузка @ShlomiAssaf должна облегчить это (хотя и не полное решение) - person Günter Zöchbauer; 18.07.2016
comment
@Shlomi - домашний мобильный телефон может отображаться на рабочем столе, если размеры настольного браузера изменятся. Пользователь может изменить размер браузера вручную. Приложение отреагирует соответствующим образом и загрузит мобильное представление в соответствии с размерами области просмотра. - person Tom Schreck; 18.07.2016
comment
@Günter Zöchbauer - спасибо. Теперь я понимаю, что вы имеете в виду. - person Tom Schreck; 18.07.2016
comment
Я не уверен, что наличие двух версий одного и того же компонента на основе медиа-запросов является правильным подходом, возможно, если вы поделитесь классом (расширите) и просто измените метаданные. Во всяком случае, в angular 1 мы довольно легко решили эту проблему с помощью веб-пакета, создав плагины, которые разбивают код на определенные асинхронные фрагменты. поэтому вы бы сделали require.diverge({mobile: './[mobile].my-comp.template.html', desktop: './[desktop].my-comp.template.html'). Это также работает для файлов CSS и JS. Однако в NG2 компилятор времени выполнения может отсутствовать, поэтому этот подход требует проверки. - person Shlomi Assaf; 18.07.2016
comment
Конечно, конечным результатом являются 2 пакета асинхронных фрагментов со всеми фрагментами кода, когда приложение загружается, вы загружаете пакет, подходящий для среды (мобильный/настольный компьютер) и загрузку. - person Shlomi Assaf; 18.07.2016
comment
Но, как написал Том, это не статично. Когда размер браузера изменяется ниже порогового значения, даже на рабочем столе будут отображаться мобильные компоненты, поэтому статическое связывание не будет работать. - person Günter Zöchbauer; 18.07.2016
comment
я преобразовал проверки мультимедиа в базовый компонент. теперь каждый компонент расширяет базовый компонент, поэтому IsMobile и IsDesktop являются общедоступными переменными базового компонента. Я больше не дублирую код в нескольких компонентах. Кажется, это лучший подход, который я могу придумать для динамической загрузки компонентов на основе медиа-запросов. Спасибо за ваш вклад. - person Tom Schreck; 19.07.2016
comment
Привет @TomShreck, не могли бы вы поделиться примером того, как вы решили эту проблему? Спасибо. - person PsyGik; 04.06.2017

Не могли бы вы избежать всей этой логики, направив маршрут на лениво загруженные модули, т.е. мобильный, рабочий стол, что угодно, заставив app.component перейти к маршруту соответствующего модуля на основе navigator.userAgent? Из https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent

мы рекомендуем искать строку «Mobi» в любом месте пользовательского агента, чтобы обнаружить мобильное устройство.

https://embed.plnkr.co/NLbyBEbNoWd9SUW7TJFS/

person Robert Brower    schedule 05.10.2017