Angular2学习教程之ng中变更检测问题详解
开发中遇到的问题
在开发中遇到一个这样的问题,代码不便透露,这里用简单的例子还原一下问题所在:
有三个组件,第一个是用来展示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发出的事件等还是会触发检测器的。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家的支持。