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 相关文章推荐
SUN的《AJAX与J2EE》全文译了
Feb 23 Javascript
JavaScript eval() 函数介绍及应用示例
Jul 29 Javascript
Javascript实现快速排序(Quicksort)的算法详解
Sep 06 Javascript
谈谈encodeURI和encodeURIComponent以及escape的区别与应用
Nov 24 Javascript
JS实现alert中显示换行的方法
Dec 17 Javascript
微信小程序 flex实现导航实例详解
Apr 26 Javascript
Layui数据表格之获取表格中所有的数据方法
Aug 20 Javascript
详解Express笔记之动态渲染HTML(新手入坑)
Dec 13 Javascript
你不知道的Vue技巧之--开发一个可以通过方法调用的组件(推荐)
Apr 15 Javascript
javascript利用键盘控制小方块的移动
Apr 20 Javascript
jQuery实现异步上传一个或多个文件
Aug 17 jQuery
在vue项目中引用Antv G2,以饼图为例讲解
Oct 28 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
php面向对象全攻略 (十五) 多态的应用
2009/09/30 PHP
php运行出现Call to undefined function curl_init()的解决方法
2010/11/02 PHP
PHP排序之二维数组的按照字母排序实现代码
2011/08/13 PHP
深入php之规范编程命名小结
2013/05/15 PHP
PHP采用自定义函数实现遍历目录下所有文件的方法
2014/08/19 PHP
PHP实现自动对图片进行滚动显示的方法
2015/03/12 PHP
基于prototype的validation.js发布2.3.4新版本,让你彻底脱离表单验证的烦恼
2006/12/06 Javascript
javascript代码编写需要注意的7个小细节小结
2011/09/21 Javascript
精心挑选的15款优秀jQuery 本特效插件和教程
2012/08/06 Javascript
JS代码判断IE6,IE7,IE8,IE9的函数代码
2013/08/02 Javascript
javascript实现的DES加密示例
2013/10/30 Javascript
学习JavaScript事件流和事件处理程序
2016/01/25 Javascript
JavaScript中解决多浏览器兼容性23个问题的快速解决方法
2016/05/19 Javascript
jQuery操作动态生成的内容的方法
2016/05/28 Javascript
vue.js入门教程之计算属性
2016/09/01 Javascript
js实现文字无缝向上滚动
2017/02/16 Javascript
从零开始学习Node.js系列教程三:图片上传和显示方法示例
2017/04/13 Javascript
详解50行代码,Node爬虫练手项目
2019/04/22 Javascript
[01:28]一分钟告诉你DOTA2 TI9不朽宝藏Ⅱ中有什么!
2019/07/09 DOTA
python避免死锁方法实例分析
2015/06/04 Python
关于Python元祖,列表,字典,集合的比较
2017/01/06 Python
Python中getpass模块无回显输入源码解析
2018/01/11 Python
对python xlrd读取datetime类型数据的方法详解
2018/12/26 Python
Python 使用元类type创建类对象常见应用详解
2019/10/17 Python
GNC健安喜美国官网:美国第一营养品牌
2016/07/22 全球购物
施华洛世奇加拿大官网:SWAROVSKI加拿大
2018/06/03 全球购物
Mountain Warehouse波兰官方网站:英国户外品牌
2019/08/29 全球购物
继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么?
2015/11/18 面试题
美术国培研修感言
2014/02/12 职场文书
党员领导干部廉洁从政承诺书
2014/03/27 职场文书
《果园机器人》教学反思
2014/04/13 职场文书
2014年幼儿园园务工作总结
2014/12/05 职场文书
详解Redis主从复制实践
2021/05/19 Redis
MySQL删除和插入数据很慢的问题解决
2021/06/03 MySQL
Python万能模板案例之matplotlib绘制甘特图
2022/04/13 Python
Oracle中日期的使用方法实例
2022/07/07 Oracle