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发出的事件等还是会触发检测器的。

总结

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

(0)

相关推荐

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

    开发中遇到的问题 在开发中遇到一个这样的问题,代码不便透露,这里用简单的例子还原一下问题所在: 有三个组件,第一个是用来展示Todo列表的组件TodoComponent,Todo是个类,包含id和name属性. @Component({ selector: 'todo-list', template: ` <p *ngFor='let item of todos'>{{ item.name }}</p> `, }) export class TodoComponent{ @Inpu

  • Go语言学习教程之goroutine和通道的示例详解

    目录 goroutine 通道 Range 和 Close Select 官方留的两道练习题 等价的二叉树 网络爬虫 源码地址 goroutine goroutine是由Go运行时管理的轻量级线程. go f(x, y, z)在一个新的goroutine中开始执行f(x, y,z). goroutines运行在相同的地址空间中,所以对共享的内存访问必须同步.sync包提供了基本的同步原语(synchronization primitives),比如互斥锁(mutual exclusion loc

  • Angular2学习教程之组件中的DOM操作详解

    前言 有时不得不面对一些需要在组件中直接操作DOM的情况,如我们的组件中存在大量的CheckBox,我们想获取到被选中的CheckBox,然而这些CheckBox是通过循环产生的,我们无法给每一个CheckBox指定一个ID,这个时候可以通过操作DOM来实现.angular API中包含有viewChild,contentChild等修饰符,这些修饰符可以返回模板中的DOM元素. 指令中的DOM操作 @Directive({ selector: 'p' }) export class TodoD

  • es7学习教程之Decorators(修饰器)详解

    本文主要给大家介绍的是关于es7 Decorators(修饰器)的相关内容,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: ES6 Decorators(修饰器) 修饰器(Decorator)是一个函数,用来修改类的行为.这是ES7的一个提案,目前Babel转码器已经支持 我们在游戏大型项目种经常会用到的方法,现在es6直接支持 想要使用Decorator的话需要我们配置一下文件夹,配置一下环境 npm install babel-plugin-transform-decorato

  • iOS逆向工程之Hopper中的ARM指令详解

    虽然前段时间ARM被日本软银收购了,但是科技是无国界的,所以呢ARM相关知识该学的学.现在看ARM指令集还是倍感亲切的,毕竟大学里开了ARM这门课,并且做了不少的实验,当时自我感觉ARM这门课学的还是可以的.虽然当时感觉学这门课以后似乎不怎么用的上,可曾想这不就用上了吗,不过之前学的都差不多忘了,还得捡起来呢.ARM指令集是精简指令集,从名字我们就能看出指令的个数比那些负责指令集要少一些.当然本篇所涉及的ARM指令集是冰山一角,不过也算是基础,可以阅读Hopper中的汇编了,实践出真知,看多了自

  • Zend Framework教程之Zend_Helpers动作助手ViewRenderer用法详解

    本文实例讲述了Zend Framework教程之Zend_Helpers动作助手ViewRenderer用法.分享给大家供大家参考,具体如下: MVC结构中视图层和控制器的解耦,以及渲染.往往是重复或者冗余的工作.如果一个完善的框架,对MVC的使用,必定会对这些操作进行合理的设计.让开发者更专注内容而不是控制逻辑结构本身.在ZendFramework中,主要是通过动作助手ViewRenderer来完成这个操作的.ViewRenderer 自动的完成在控制器内建立视图对象并渲染视图的过程: Vie

  • python爬虫教程之bs4解析和xpath解析详解

    目录 bs4解析 原理: 如何实例化BeautifulSoup对象: 用于数据解析的方法和属性: xpath解析 xpath解析原理: 实例化一个etree对象: xpath(‘xpath表达式’) 总结 bs4解析 原理: 1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中 2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取 如何实例化BeautifulSoup对象: from bs4 import BeautifulSoup Be

  • JavaScript进阶教程之非extends的组合继承详解

    目录 前言 一:call() 的作用与使用 1.1 使用 call() 来调用函数 1.2 使用 call() 来改变 this 的指向 二:利用构造函数继承父属性 2.1 实现过程 2.1 实现过程分析 三:利用原型对象继承父方法 3.1 继承父方法的错误演示 3.2 继承父方法的正确做法 3.2 继承父方法的注意事项 总结 前言 继承也是面向对象的特性之一,但是在 ES6 版本之前是没有 extends 去实现继承的,我们只能通过 构造函数 和 原型对象 来实现继承,其中分别为构造函数来继承

  • JSP学习之Java Web中的安全控制实例详解

    本文实例讲述了JSP学习之Java Web中的安全控制.分享给大家供大家参考.具体如下: 一.目标: ① 掌握登录之后的一般处理过程: ② 能够为每个页面添加安全控制: ③ 能够共享验证代码: ④ 使用过滤器对权限进行验证: ⑤ 能够对文件的局部内容进行验证: ⑥ 掌握安全验证码的基本实现方式: ⑦ 通过异常处理增强安全性. 二.主要内容: ① 通过修改前面的登录功能,分别对管理员和普通用户的登录进行处理: ② 为管理员才能访问的页面添加控制: ③ 共享各个页面中的控制代码,使用专门的文件,然后

  • Angular2学习教程之TemplateRef和ViewContainerRef详解

    TemplateRef 在介绍 TemplateRef 前,我们先来了解一下 HTML 模板元素 - <template> .模板元素是一种机制,允许包含加载页面时不渲染,但又可以随后通过 JavaScript 进行实例化的客户端内容.我们可以将模板视作为存储在页面上稍后使用的一小段内容. 在 HTML5 标准引入 template 模板元素之前,我们都是使用 <script> 标签进行客户端模板的定义,具体如下: <script id="tpl-mock"

随机推荐