详解Angular组件数据不能实时更新到视图上的问题

目录
  • 问题起源
  • OnPush策略
    • 当前组件或子组件之一触发了事件
  • 总结

问题起源

MainComponent:

@Component({
  selector: 'main',
  template: `
    <MenuComponent [isReport]="isReport">
	  </MenuComponent>
 `,
  changeDetection:ChangeDetectionStrategy.OnPush
})
export class MainComponent {
  ...
}

现在有一个MainComponent,我需要在这个组件中引用一个另一个组件MenuComponent

import { Component, Input} from '@angular/core';
import { Subject, debounceTime } from 'rxjs';
@Component({
  selector: 'movie',
  styles: ['div {border: 1px solid black}'],
  template: `
    <div (mouseover)="mouseOver()">
      <h3>{{ menu }}</h3>
    </div>`
})
export class MovieComponent {
  @Input() isReport: boolean = false;
  menu: string = '我是Menu';
  mouseOver$: Subject<any> = new Subject();
  ngOnInit(): void {
    this.mouseOver$.pipe(debounceTime(250)).subscribe((data) => {
       this.menu = 'New: ' + this.menu;
    });
  }
  mouseOver(): void {
    this.mouseOver$.next(this.menu);
  }
}

这个MenuComponent在其他的页面使用起来是正常的,而且因为是Menu组件,所以上面很有多mouseover事件,这些事件也可以正常工作。但,这个 MenuComponent 放在MainComponent中,mouseover事件就有问题了,调试了下mouseover事件,代码都正确执行了,感觉代码并没有什么问题。因为这个组件放在其他页面里,行为完全正常,所以感觉不是组件本身的问题。

表现的现象是

Menu里的mouseover行为很怪异,你over到A的时候,显示的是B的数据,当你over到B的时候显示的是A的数据,整个错乱了。

第一反应就是,这会不会是和MainComponent中的mouseover事件冲突了呢?

检查了一遍,没有发现问题所在。但是有意外收获,啊啊啊,MainComponent组件使用的是OnPush变更检测策略,难怪其他页面都好使,就这个地方有问题了。好了,问题应该就是OnPush造成的。关于变更检测策略的,那还不是手到擒来,在熟悉不过了,来来来,一起简单看一下这个OnPush。

OnPush策略

Angular有两种变更检测的策略,一种是Default,另一种就是这个OnPush。OnPush这个变更检测策略主要为了改善性能。当我们设置组件装饰器的 changeDetection为OnPush的时候,Angular 每次触发变更检测后会跳过该组件和该组件的所以子组件变化检测

好了,我们也知道什么是OnPush变更检测策略了,它会跳过当前组件和其子组件的变更 检测。也就是说,你改变这个组件的属性值,但这些属性值并不会更新到视图上,也就是组件数组和视图不一致。那我们知道了这一点,再回去看一下MenuComponent

由于MainComponent的变更策略设置为了OnPush,他的子组件的变更检测策略会跳过,也就是MenuComponent变更检测不起作用了。但是,你会发现当你操作Menu的时候视图还是会有变化的。这是怎么回事?

大部分人可能花一分钟了解了OnPush是什么,但是没有了解透彻。继续往下看。

OnPush 策略下,以下这种情况会触发组件的变化检测:

当前组件或子组件之一触发了事件

如果OnPush组件或其子组件之一触发(DOM/BOM)事件,例如 clickmouseovermouseleaveresize, keydown,则将触发变化检测(针对组件树中的所有组件)。

需要注意的是在OnPush策略中,以下操作不会触发变化检测:

  • setTimeout()
  • setInterval()
  • Promise.resolve().then()
  • this.http.get('...').subscribe()

原来如此,尽管是OnPush策略,但是DOM/BOM事件还是会触发变更检测的,所以MenuComponent的视图还是会有变化的,也就是这个变更检测是起作用的。但问题还是没有解决,Menu mouseover的时候还是会错乱啊!再来看一下代码。

ngOnInit(): void {
    this.mouseOver$.pipe(debounceTime(250)).subscribe((data) => {
       this.menu = 'New: ' + this.menu;
    });
}

引起问题的地方就是这debounceTime,这个之前在介绍Rxjs原理的时候,说过这个是异步的。之前掌握的东西,终于派上用场了。

总结一下,就是mouseover是异步的,会触发变更检测,但是由于debounceTime是异步又嵌套了一下,debounceTime一般是用setTimeout来实现的。所以,debounceTime里的数据变化并不能及时的显示到视图中。终于找到问题的根源了。啦啦啦。问题找到了,那解决起来多easy啊。它不是不会触发变更检测吗,我就手动让它触发一下吧。

import { Component, Input, ChangeDetectorRef } from '@angular/core';
import { Subject, debounceTime } from 'rxjs';
@Component({
  selector: 'movie',
  styles: ['div {border: 1px solid black}'],
  template: `...`
})
export class MovieComponent {
  ...
  constructor(private cd: ChangeDetectorRef){}
  ngOnInit(): void {
    this.mouseOver$.pipe(debounceTime(250)).subscribe((data) => {
       this.menu = 'New: ' + this.menu;
       this.cd.detectChanges();
    });
  }
  ...
}

总结

  • 平时多注意知识积累,不能按照网上说的解决方案复制过来就解决了,遇到简单问题这样是没有问题的,遇到复杂的就没办法了;
  • 当设置为Onpush策略时,要更加注意,用OnPush就是要减少变更检测的次数,就不要无论什么情况都detectChanges,或markForCheck,失去了意义,还是要规范使用;
  • 要优雅实现代码,项目中居然还看到把父组件的ChangeDetectorRef作为输入属性传到子组件中,一看就不懂变更检测啊;

以上就是详解Angular组件数据不能实时更新到视图上的问题的详细内容,更多关于Angular组件数据实时更新视图的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解Angular组件之中间人模式

    一.中间人模式 该组件树中除了组件1以外,每个组件都有一个父组件可以扮演中间人的角色.顶级的中间人是组件1,它可以使组件2,组件3,组件6之间互相通讯.依次类推,组件2是组件4和组件5的中间人.组件3是组件7和组件8的中间人. 中间人负责从一个组件接收数据并将其传递给另一个组件. 二.例子 股票报价组件为例,假设交易员在监看着报价组件的价格,当股票价格达到某一个值的时候,交易员会点一个购买按钮,来购买股票.问题:报价组件并不知道应该如何下单来买股票,它只是用来监控股票价格的.所以报价组件在这时应

  • 详解Angular父子组件通讯

    概述 Angular组件间通讯 组件树,1号是根组件AppComponent. 组件之间松耦合,组件之间知道的越少越好. 组件4里面点击按钮,触发组件5的初始化逻辑. 传统做法:在按钮4的点击事件里调用组件5的方法.紧密耦合. Angular:在组件4根本不知道组件5存在的情况下实现. 使用松耦合的方式在组件之间传递数据开发出高重用性的组件. 使用输入输出属性在父子关系的组件之间传递数据. 一.输入输出属性概述 组件设计成黑盒模型,用输入属性声明从外部世界接收什么东西.不需要知道这些东西从哪里来

  • 详解Angular组件之投影

    概述 运行时动态改变组件模版的内容.没路由那么复杂,只是一段html,没有业务逻辑. ngContent指令将父组件模版上的任意片段投影到子组件上. 一.简单例子 1.子组件中使用<ng-content>指令来标记投影点 <div class="wrapper"> <h2>我是子组件</h2> <div>这个div定义在子组件中</div> <ng-content></ng-content>

  • 如何通过简单的代码描述Angular父组件、子组件传值

    目录 引言 零.知识铺垫 CSS选择器 一.什么是父子组件 二.父组件调用子组件的方法 三.父组件向子组件传值 子组件使用@input装饰器接收数据 父组件使用方括号[]发送数据 升级:子组件通过set方法监听传入数据变化 另一种升级:子组件通过ngOnChanges()生命周期钩子监听传入数据变化 四.子组件向父组件传值 子组件向父组件弹射事件 父组件监听子组件弹射的事件 五.总结 六.后记 总结 引言 对于稍微接触过Angular组件的同学来说,组件间交互应该没有什么问题. 本文想追求的是用

  • Angular懒加载动态创建显示该模块下声明的组件

    目录 环境: Angular 13.x.x 新建一个angular项目 懒加载工具栏组件 toolbar.module.ts toolbar.component.ts toolbar.component.html app.component.ts 环境: Angular 13.x.x angular中支持可以通过路由来懒加载某些页面模块已达到减少首屏尺寸, 提高首屏加载速度的目的. 但是这种通过路由的方式有时候是无法满足需求的. 比如, 点击一个按钮后显示一行工具栏, 这个工具栏组件我不希望它默

  • Angular组件拿不到@Input输入属性问题探究解决方法

    目录 区别一 区别二 区别三 总结 我需要给一个Component设置一个输入属性 @Input,好了,直接上代码,没有什么难度. 原代码是这样的: @Component({ selector: 'my-menu', templateUrl: './main-menu.widget.html' }) export class MyMenuWidget { data: any[]; ... constructor(...) { this._changesSubscription = this._s

  • 简单谈谈Angular中的独立组件的使用

    目录 前言 如何创建一个独立组件 在独立组件中导入已有的模块 使用独立组件启动Angular应用 为独立组件配置路由 配置依赖注入 源代码 前言 Angular 14一项令人兴奋的特性就是Angular的独立组件终于来了. 在Angular 14中, 开发者可以尝试使用独立组件开发各种组件,但是值得注意的是Angular独立组件的API仍然没有稳定下,将来可能存在一些破坏性更新,所以不推荐在生产环境中使用. 如何创建一个独立组件 对于已有的组件,我们可以在@Component()中添加stand

  • 详解Angular组件数据不能实时更新到视图上的问题

    目录 问题起源 OnPush策略 当前组件或子组件之一触发了事件 总结 问题起源 MainComponent: @Component({ selector: 'main', template: ` <MenuComponent [isReport]="isReport"> </MenuComponent> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class MainComponent

  • 详解Angular组件生命周期(一)

    概述 组件声明周期以及angular的变化发现机制 红色方法只执行一次. 变更检测执行的绿色方法和和组件初始化阶段执行的绿色方法是一个方法. 总共9个方法. 每个钩子都是@angular/core库里定义的接口. import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-life', templateUrl: './life.component.html', styleUrls: ['./life

  • 详解Angular组件之生命周期(二)

    一.view钩子 view钩子有2个,ngAfterViewInit和ngAfterViewChecked钩子. 1.实现ngAfterViewInit和ngAfterViewChecked钩子时注意事项 以父组件调用子组件方法中例子为基础,在父组件中实现ngAfterViewInit和ngAfterViewChecked钩子. 这两个钩子是在组件的模版所有内容组装完成后,组件模版已经呈现给用户看了,之后这两个钩子方法会被调用. @ViewChild('child1') child1:Child

  • 对angular 实时更新模板视图的方法$apply详解

    有的时候在回调里面写了更新scope的里面的内容,视图上面竟然没有同时更新,这就用到了$apply Scope的特性 接下来,看看Scope有哪些特性呢? Scope提供$watch方法监视Model的变化. Scope提供$apply方法传播Model的变化. Scope可以继承,用来隔离不同的application components和属性访问权限. Scope为Expressions的计算提供上下文. 最简单的使用方法,就是在需要传递变化的地方写上以下代码 $scope.$apply()

  • 详解Angular动态组件

    使用场景 我们先明确下动态组件的使用场景,在代码运行时要动态加载组件,换成普通人话,代码需要根据具体情况(比如用户的操作,向后台请求结果)确定在某些地方加载某些组件,这些组件不是静态的(不是固定的). 官网的举例就是,构建动态广告条,广告组件不断会推出新的,再用只支持静态组件结构的模板显然是不现实的. 再举一个常见的例子,动态弹出框,弹出的组件是不确定的.不断更新的,这里那里弹出个购买框,那那那又需要弹出样式选择框,静态组件结构模板是不能满足群众日渐增长的需求. 怎么实现 然后我们来找个把手,看

  • 详解Angular数据绑定及其实现方式

    前言 Web开发需要模型和视图之间的数据同步.这些模型基本上包含数据值,而视图则处理用户看到的内容.因此,如果您想知道这在Angular中是如何发生的,这篇有关Angular数据绑定的文章将为您提供帮助. 下面提到的是此处讨论的主题: What is Data Binding? Types of Data Binding in Angular One-way Data Binding Interpolation Property Binding Event Binding Two-way Dat

  • 详解vue的数据binding绑定原理

    自从angular火了以后,各种mvc框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点-性能低下了.有时候改了一个地方,脏循环要循环多次来保证数据是不是真的变了和是否停止变化了.这样性能就很低了.于是人们开始钻研新的双向数据binding的方法.尤大的vue binding就是本人蛮喜欢的一种实现方式,本文跟随尤大的一个例子来详解vue的数据binding的原理. 数据binding,一般

随机推荐