如何在Angular2中做响应式组件

Posted

技术标签:

【中文标题】如何在Angular2中做响应式组件【英文标题】:how to do responsive components in Angular2 【发布时间】:2016-11-21 09:04:32 【问题描述】:

我正在涉足 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 确定要加载哪个子组件。这是否可以扩展以在完整的生产级应用程序中工作?我对您对更好方法的反馈非常感兴趣,或者这种方法对于完整的生产应用程序是否可以接受和可扩展。感谢您的反馈。

【问题讨论】:

【参考方案1】:

您可以创建自定义媒体感知 *ngIf*ngSwitch 结构指令以减少重复。

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

【讨论】:

Tom,这种设计将使桌面组件和移动组件存在于同一个构建中,这意味着一个包含未使用代码的大包。您将无法在使用时对其进行摇树,但实际上并未使用。例如,&lt;home-mobile&gt; 永远不会在桌面上运行,对吧? 感谢您的回复。在您提供的 URL 中,我找不到任何关于媒体感知结构指令的具体内容。我正在使用 ngIf 结构指令来加载子组件,并将 MatchMediaService 用于媒体方面,为 ngIf 提供决策点。我错过了什么? 你有更好的主意吗? Angular 团队似乎想提供一些开箱即用的东西,但无法提出一些好的方法。 @View() 应该在这里提供帮助,但因为没有成功而被放弃。 但正如 Tom 所写,这不是静态的。当浏览器的大小调整到低于阈值时,即使在桌面移动组件上也会显示,因此静态捆绑将不起作用。 我已将媒体检查重构为基本组件。现在每个组件都扩展了基础组件,因此 IsMobile 和 IsDesktop 是基础组件上的公共变量。我不再跨多个组件复制代码。这似乎是我能想到的基于媒体查询动态加载组件的最佳方法。感谢您的意见。【参考方案2】:

难道你不能通过路由到延迟加载的模块来避免所有这些逻辑,即。移动、桌面等,通过让 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/

【讨论】:

以上是关于如何在Angular2中做响应式组件的主要内容,如果未能解决你的问题,请参考以下文章

Angular2 动态生成响应式表单

Angular2,如何只重新加载一次组件?

将Angular2服务注入组件基类

如何在服务中初始化响应式表单而不是注入到组件中

Angular2 - 如何将表单上的“touched”属性设置为true

为啥我应该在构造函数而不是 ngOnInit 中创建我的 Angular2 响应式表单?