用于检测屏幕尺寸的 Angular 指令

Posted

技术标签:

【中文标题】用于检测屏幕尺寸的 Angular 指令【英文标题】:Angular directive for detect screen size 【发布时间】:2020-11-18 21:38:02 【问题描述】:

请帮帮我,我该怎么做?

我想对 div 应用指令,它将根据其值显示或隐藏内容,例如:*ifViewportSize="'mobile'"

<div *ifViewportSize="'large'">PC</div>
<div *ifViewportSize="'small'">Mobile</div>

指令:(rezult in console.log)https://stackblitz.com/edit/angular-ivy-wdl8ee

@Directive(
  selector: '[ifViewportSize]'
)
export class IfViewportSizeDirective 

size: string;

  config = 
    large: 992,
    medium: 768,
    small: 576
  ;

  constructor(
    private elemRef: ElementRef,
    private vcRef: ViewContainerRef,
    private templRef: TemplateRef<any>) 

    window.onresize = (event) => 
      this.showElem();
    ;
  

  @Input() set ifViewportSize(size: string) 
    this.size = size;
  

  ngOnInit() 
    this.showElem();
  

  showElem() 
    console.log('size: ',this.size);

    if (this.config[this.size] < window.innerWidth) 
      this.vcRef.clear();
      this.vcRef.createEmbeddedView(this.templRef);
    
    else this.vcRef.clear();
  


指令仅在最后一个 div 中有效。请告诉我为什么?

我还尝试创建(就在这里,在 stackblitz 上)单独的指令 ifMobile и ifTablet。 我在那里实现了一个函数window.onresize,但同样这个函数只适用于最后一个div。

我该如何解决? 如果这是检测屏幕尺寸的错误方法,我该怎么做呢? 非常感谢!

【问题讨论】:

您可以使用material.angular.io/cdk/layout/overview 找到解决方案,而不是重新发明***。使用该包,您可以监听组件中的媒体查询更改并采取相应措施 在您的案例中,移动、桌面和 PC 屏幕的界限是什么? 0 - 576 - 手机,576 - 768 - 平板电脑,768 - 992 - PC? @Jelle 但我必须在每个组件中监听 breakpointObserver。也许有没有办法不复制所有组件中的代码? @yurzui 是的,但这是例如,我试图了解如何做这样的问题并找到最佳实践。 @kompaniietst 您始终可以在单独的服务中创建观察者。与真正想要收听它的组件共享它 【参考方案1】:

更新

最好的解决方案不是重新发明***,而是使用@angular/cdk/layout 功能:

if-viewport-size.directive.ts

type Size = 'small' | 'medium' | 'large';

const config = 
  small: [Breakpoints.Small, Breakpoints.XSmall],
  medium: [Breakpoints.Medium],
  large: [Breakpoints.Large, Breakpoints.XLarge]
;

@Directive(
  selector: "[ifViewportSize]"
)
export class IfViewportSizeDirective implements OnDestroy 
  private subscription = new Subscription();

  @Input("ifViewportSize") set size(value: Size) 
    this.subscription.unsubscribe();
    this.subscription = this.observer
      .observe(config[value])
      .subscribe(this.updateView);
  

  constructor(
    private observer: BreakpointObserver,
    private vcRef: ViewContainerRef,
    private templateRef: TemplateRef<any>
  ) 

  updateView = ( matches : BreakpointState) => 
    if (matches && !this.vcRef.length) 
      this.vcRef.createEmbeddedView(this.templateRef);
     else if (!matches && this.vcRef.length) 
      this.vcRef.clear();
    
  ;

  ngOnDestroy() 
    this.subscription.unsubscribe();
  

用法:

<div *ifViewportSize="'large'">PC</div>
<div *ifViewportSize="'medium'">Tablet</div>
<div *ifViewportSize="'small'">Mobile </div>

Stackblitz Example

使用此解决方案的好处:

    避免内存泄漏

    Resize 事件以高效的方式列出(在 Angular 区域之外)

    它不会在调整大小时无限删除和重新创建模板,而是仅在遇到断点时才删除和重新创建模板

上一个答案

它仅适用于最后一个 div,因为您正在通过 onresize 属性监听调整大小事件,该属性被每个指令覆盖

window.onresize = (event) => 
   this.showElem();
;

window.onresize = (event) =>  <---- you completely replace onresize event
  this.showElem();
;
...
window.onresize = (event) =>  <---- one more replacement
  this.showElem(); <-------------- only this handler will be executed 
;

您可以改用@HostListener,但它是doesn't work in structural directives。所以尝试改用window.addEventListener

ngOnInit() 
 window.addEventListener('resize', this.showElem);
 ...


showElem = () =>  // <-- note syntax here
   ...


ngOnDestroy() 
  window.removeEventListener('resize', this.showElem);

注意:我也会考虑收听resize,即使是在外面 NgZone 来自ngZone.runOutsideAngular()

现在,您的调整大小处理程序应该可以工作了。

Forked Stackblitz

但是

你一直在删除模板

目前还不清楚您将如何处理所有尺寸的边界。因为手机屏幕满足条件mobileSize &lt; window.innerWidth

【讨论】:

非常感谢!它看起来合乎逻辑并且有效。但我能问一下,也许你知道其他更好的解决方案,比如我的? 是的,请看一下。我添加了替代选项【参考方案2】:

您可以利用let directive with $implicit context 的角度与structural directive 一起使用,如下所示,


<div *ifViewportSize="let view=$implicit"> .... </div>

import  Directive, ElementRef, ViewContainerRef, TemplateRef, OnInit, Input, HostListener  from '@angular/core';

export enum VIEW
  MOBILE ,
  TABLET ,
  PC 


@Directive(
  selector: '[responsive]'
)
export class IfViewportSizeDirective 

  view;

  config = 
    large: 1200,
    medium: 700,
    small: 500
  ;

  constructor(private readonly viewRef: ViewContainerRef,
        private readonly templateRef: TemplateRef<any>)   this.checkView();

  ngOnInit()
      window.addEventListener('resize', this.checkView.bind(this));
  

 
  checkView() 
    this.viewRef.clear();

     console.log(window.innerWidth);
     if((0 < window.innerWidth) && (window.innerWidth < this.config['small'] ))
       
       this.view = VIEW.MOBILE;
       
      else if((this.config['small'] < window.innerWidth) && (window.innerWidth < this.config['medium']))

       this.view = VIEW.TABLET;
       
      else if(this.config['medium'] < window.innerWidth)
      
      this.view = VIEW.PC;
     
     
  
     this.viewRef.createEmbeddedView(this.templateRef,
                    $implicit: this.view
     );
    
  


FULL DEMO

注意您仍然可以优化它。

【讨论】:

我会指出这个解决方案的几个问题:1. 你永远不会从这段代码中删除监听器window.addEventListener('resize', this.checkView.bind(this)); 2. 你总是在调整大小时删除模板。 3 您在 Angular 区域内运行代码,这可能会导致性能问题 如果您查看了我的最后一条评论it still can be improved。 1) 您可以删除ngDestory 上的侦听器。 2)而不是总是删除/更新模板,逻辑可以检测何时删除/附加视图。 3) Angular zone :这可以被认为是正确地在区域之外运行并手动触发更改检测。这只是一些提醒,所以 OP 可以开始。 肯定可以优化。有时,重新发明***也可以学习新知识并根据我们的要求对其进行定制。 1.如果使用.bind添加,则无法在 ngDestroy 中删除监听器 我认为,您没有理解 IT CAN BE IMPROVED 这句话。我不是来编写无错误或无内存泄漏的代码。如果这是目的,许多第三方工具可供选择。你只是指出并走开,但那是行不通的。那不是目的。 OP 自己尝试了一些东西,并希望拥有比他实施的更好的东西。就是这样! 我不想伤害你,我得到了句子 IT CAN BE IMPROVED 但是如果它最初写有错误,对于 OP 很难改进(我意思是addEventListenerbind)

以上是关于用于检测屏幕尺寸的 Angular 指令的主要内容,如果未能解决你的问题,请参考以下文章

Xcode - 如何检测 iPhone 的屏幕尺寸 [关闭]

Android 模拟器没有检测到正确的屏幕尺寸?

Angular Material Design - 随屏幕尺寸改变 flex 值

如何在 React 中检测屏幕尺寸是不是已更改为移动设备?

设备上的 as3 空气和屏幕尺寸检测问题

如何在OpenGL中自动选择屏幕尺寸