如何在Angular2中创建响应式组件

22

我正在学习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确定要加载哪个子组件。这种方法在完整的生产级应用程序中是否可扩展?我非常希望得到您的反馈,看看是否有更好的方法,或者这种方法是否可接受并且可扩展到完整的生产应用程序中。感谢您的反馈。

2个回答

4

1
汤姆,这个设计将使桌面组件和移动组件在同一个构建中运行,这意味着一个庞大的包含未使用代码的捆绑包。你将无法像以前那样进行树摇优化,因为有些代码虽然被使用了,但并没有真正被使用。 例如,<home-mobile> 组件永远不会在桌面上运行,对吧? - Shlomi Assaf
1
你有更好的想法吗?似乎Angular团队想要提供一些开箱即用的东西,但是他们无法想出一个好的方法。@View()原本应该在这方面有所帮助,但因为效果不佳而被放弃了。 - Günter Zöchbauer
1
但正如Tom所写,这并不是静态的。当浏览器调整大小低于阈值时,即使在桌面上也会显示移动组件,因此静态捆绑将无法工作。 - Günter Zöchbauer
3
我已将媒体检查重构为基础组件。现在每个组件都扩展了基础组件,因此IsMobile和IsDesktop是基础组件上的公共变量。我不再在多个组件中重复代码。这似乎是我能想到的根据媒体查询动态加载组件的最佳方法。感谢您的建议。 - Tom Schreck
嗨@TomSchreck,你能否分享一下你是如何解决这个问题的样例?谢谢。 - PsyGik
显示剩余7条评论

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接