Angular2学习教程之ng中变更检测问题详解


Posted in Javascript onMay 28, 2017

开发中遇到的问题

在开发中遇到一个这样的问题,代码不便透露,这里用简单的例子还原一下问题所在:

有三个组件,第一个是用来展示Todo列表的组件TodoComponent,Todo是个类,包含id和name属性。

@Component({
 selector: 'todo-list',
 template: `
  <p *ngFor='let item of todos'>{{ item.name }}</p>
 `,
})
export class TodoComponent{
 @Input() todos: Todo[];

 public getTodos():Todo[]{
  return this.todos;
 }
}

第二个组件同样是一个Todo列表展示组件TodoDataComponent ,不同的是该组件需要一个TodoComponent类型的输入,并从TodoComponent组件中获得需要展示的Todo数据。

@Component({
 selector: 'app-todo-data',
 template: `<p *ngFor='let item of todos'>{{ item.name }}</p>
  <button (click)='getData()'>get data</button>`,
 styleUrls: ['./todo-data.component.css'],
 inputs: ['todoComponent'],
})
export class TodoDataComponent implements OnInit {
 todoComponent: TodoComponent;
 todos: Todo[]
 constructor() { }
 ngOnInit() {
 }
 getData(){
  this.todos=this.todoComponent.getTodos();
 }
}

最后一个是应用的根组件,根组件根据loading值来确定是否加载TodoComponent组件,并展示TodoDataComponent 组件。

//app.component.htm
<div>
 <div *ngIf='loading'>
  <todo-list [todos]='todos'></todo-list>
  <button (click)='changeall()'>next</button>
 </div>
</div>
<div>
 <app-todo-data [todoComponent]='todoComponent'></app-todo-data>
</div>

//app.component.ts
@Component({
 selector: 'app-root',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
 todos: Todo[];
 @ViewChild(TodoComponent)
 todoComponent: TodoComponent;
 loading: boolean = true;
 constructor(private todoService:TodoService){
  super(true);
 }
 ngOnInit(){
  this.todoService.todos.subscribe(data => {this.todos=data});
  this.todoService.load(0, 3);
 }
 changeall(){
  this.todoService.load(3, 3);
 }
}

这样问题就来了,TodoComponent 组件是否在页面上展示是不确定的,在上面的例子中根组件最开始没有渲染TodoComponent组件,最后根据loading的值将TodoComponent渲染出来。而TodoDataComponent 组件的显示又需要一个TodoComponent 进行初始化(跟组件通过@ViewChild(TodoComponent)获得),这样造成在开发模式下出现以下错误:
template:9:16 caused by: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '[object Object]'.

该错误仅在开发模式下会报告出来的,解决掉总是更好的选择,防止在生产环境下出现问题。

问题的原因及解决办法

这个问题是ng2中的变更检测策略造成的,ng2并没有智能到一有数据变更就能自动检测到的,执行变更检测的一些情况有:组件中的输入发生变化、组件中有事件响应、setTimeOut函数等。

这样在上面的小例子中, @ViewChild(TodoComponent)todoComponent: TodoComponent;从undefined到[object Object],而并没有触发ng的变更检测。

解决办法也很简单,ng支持手动触发变更检测,只要在适当的位置,调用变更检测即可。
在上面的例子中,解决办法为:

从@angular/core引入AfterViewInit, ChangeDetectorRef。注入ChangeDetectorRef对象,并在声明周期钩子ngAfterViewInit中调用变更

constructor(private todoService:TodoService, private cdr: ChangeDetectorRef){}

ngAfterViewInit(){
 this.cdr.detectChanges();
}

ChangeDetectorRef

用来处理ng变更的类,可以使用它来进行完全的手动变更检测,主要有一下方法:

1.markForCheck()标记为需要进行变更检测,官方给的一下例子,setInterval不会触发变更检测,因此模板上的numberOfTicks 并不会发生变化。

setInterval(() => {
this.numberOfTicks ++
 // the following is required, otherwise the view will not be updated
 this.ref.markForCheck();
}, 1000);

2.detach()从变更检测树上分离,即该组件不会进行自动的变更检测,变更需要手动进行,使用detectChanges函数。

3.detectChanges()手动检测变更,当变更检测代价较大时,可以设置为定时进行表更检测

ref.detach();
setInterval(() => {
 this.ref.detectChanges();
}, 5000);

4.checkNoChanges()进行变更检测,有变更时抛出异常

5.reattach()detach()方法的作用相反

其他一些变更检测知识

angular2中的每一个组件都关联到一个变更检测器,ChangeDetectorRef可以用来控制变更检测器进行检测。
浏览器的以下行为可以出发检测器进行检测:

1.所有浏览器事件

2.setTimeout()setInterval()

3.Ajax请求

OnPush变更检测模式

组件默认使用的是Default变更检测模式,只要组件的输入发生变化时,就会触发检测器的执行。除Default模式外,还有一种OnPush变更检测模式,使用该模式首先需要在组件声明修饰符中添加

@Component({
 selector: 'todo-list',
 changeDetection: ChangeDetectionStrategy.OnPush,
})

声明为OnPush变更检测模式意味着当组件输入发生变化时,不一定会触发变更检测器,只有当该输入的引用发生变化时,检测器才会触发。例如在一个数组中某个下标的值发生变化时,检测器不会触发,视图不会更新,只有该数组引用发生变化时,视图才会更新。当然浏览器事件、observable发出的事件等还是会触发检测器的。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家三水点靠木的支持。

Javascript 相关文章推荐
SWFObject Flash js调用类
Jul 08 Javascript
JavaScript之HTMLCollection接口代码
Apr 27 Javascript
js判断样式className同时增加class或删除class
Jan 30 Javascript
jquery多选项卡效果实例代码(附效果图)
Mar 23 Javascript
使用闭包对setTimeout进行简单封装避免出错
Jul 10 Javascript
js验证电话号码与手机支持+86的正则表达式
Jan 23 Javascript
jQuery实现的网页换肤效果示例
Sep 20 Javascript
JS识别浏览器类型(电脑浏览器和手机浏览器)
Nov 18 Javascript
node中IO以及定时器优先级详解
May 10 Javascript
js实现简单的日历显示效果函数示例
Nov 25 Javascript
Js视频播放器插件Video.js使用方法详解
Feb 04 Javascript
微信小程序实现上拉加载功能示例【加载更多数据/触底加载/点击加载更多数据】
May 29 Javascript
Angular2使用jQuery的方法教程
May 28 #jQuery
Angular.js实现动态加载组件详解
May 28 #Javascript
利用node.js如何搭建一个简易的即时响应服务器
May 28 #Javascript
利用Angular.js编写公共提示模块的方法教程
May 28 #Javascript
Angular2入门教程之模块和组件详解
May 28 #Javascript
关于Angular2 + node接口调试的解决方案
May 28 #Javascript
对象不支持indexOf属性或方法的解决方法(必看)
May 28 #Javascript
You might like
简单的页面缓冲技术
2006/10/09 PHP
UCenter 批量添加用户的php代码
2012/07/17 PHP
PHP 基于Yii框架中使用smarty模板的方法详解
2013/06/13 PHP
浅谈使用PHP开发微信支付的流程
2015/10/04 PHP
PHP Try-catch 语句使用技巧
2016/02/28 PHP
Zend Framework前端控制器用法示例
2016/12/11 PHP
thinkphp5 URL和路由的功能详解与实例
2017/12/26 PHP
JS实现打开本地文件或文件夹
2021/03/09 Javascript
基于Asp.net与Javascript控制的日期控件
2010/05/22 Javascript
js自动下载文件到本地的实现代码
2013/04/28 Javascript
Extjs407 getValue()和getRawValue()区别介绍
2013/05/21 Javascript
禁止iframe页面的所有js脚本如alert及弹出窗口等
2014/09/03 Javascript
AngularJS ng-template寄宿方式用法分析
2016/11/07 Javascript
AngularJS ui-router (嵌套路由)实例
2017/03/10 Javascript
jQuery tip提示插件(实例分享)
2017/04/28 jQuery
JS匹配日期和时间的正则表达式示例
2017/05/12 Javascript
Vue下路由History模式打包后页面空白的解决方法
2018/06/29 Javascript
详解Vue中使用Echarts的两种方式
2018/07/03 Javascript
JQuery Ajax执行跨域请求数据的解决方案
2018/12/10 jQuery
详解vue挂载到dom上会发生什么
2019/01/20 Javascript
layui 实现加载动画以及非真实加载进度的方法
2019/09/23 Javascript
Vue实现数据请求拦截
2019/10/23 Javascript
jQuery中getJSON跨域原理的深入讲解
2020/09/02 jQuery
[02:32]“虐狗”镜头慎点 2016国际邀请赛中国区预选赛现场玩家采访
2016/06/28 DOTA
基于python进行桶排序与基数排序的总结
2018/05/29 Python
Django 路由层URLconf的实现
2019/12/30 Python
解析PyCharm Python运行权限问题
2020/01/08 Python
Python continue语句实例用法
2020/02/06 Python
美国知名户外用品畅销中心:Sierra Trading Post
2016/07/19 全球购物
捷克钓鱼用品网上商店:Parys.cz
2018/06/15 全球购物
C语言编程练习
2012/04/02 面试题
大学秋游活动方案
2014/02/11 职场文书
《中华上下五千年》读后感3篇
2019/11/29 职场文书
Python Pandas知识点之缺失值处理详解
2021/05/11 Python
nodejs利用readline提示输入内容实例代码
2021/07/15 NodeJs
python中数组和列表的简单实例
2022/03/25 Python