该指令用于基于可迭代对象中的每一项创建相应的模板。每个实例化模板的上下文对象继承于外部的上下文对象,其值与可迭代对象对应项的值相关联。

NgForOf 指令语法

* 语法糖

  1. <li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>

template语法

  1. <li template="ngFor let item of items; index as i; trackBy: trackByFn">...</li>

<ng-template> 元素

  1. <ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
  2. <li>...</li>
  3. </ng-template>
  4. <!--等价于-->
  5. <ng-template ngFor let-item="$implicit" [ngForOf]="items" let-i="index"
  6. [ngForTrackBy]="trackByFn">
  7. <li>...</li>
  8. </ng-template>

NgForOf 使用示例

  1. @Component({
  2. selector: 'exe-app',
  3. template: `
  4. <ul>
  5. <li *ngFor="let item of items; let i = index">
  6. {{i}}. {{item}}
  7. </li>
  8. </ul>
  9. `
  10. })
  11. export class AppComponent {
  12. items = ['First', 'Second', 'Third'];
  13. }

基础知识

NgForOfContext

NgForOfContext 实例用于表示 NgForOf 上下文。

  1. // packages/common/src/directives/ng_for_of.ts
  2. export class NgForOfContext<T> {
  3. constructor(
  4. public $implicit: T,
  5. public ngForOf: NgIterable<T>,
  6. public index: number,
  7. public count: number) {}
  8.  
  9. get first(): boolean { return this.index === ; }
  10.  
  11. get last(): boolean { return this.index === this.count - ; }
  12.  
  13. get even(): boolean { return this.index % === ; }
  14.  
  15. get odd(): boolean { return !this.even; }
  16. }
  17.  
  18. // 定义可迭代的类型
  19. export type NgIterable<T> = Array<T>| Iterable<T>;

Local Variables

NgForOf 提供了几个导出值,可以将其替换为局部变量:

  • $implicit: T - 表示 ngForOf 绑定的可迭代对象中的每一个独立项。

  • ngForOf: NgIterable<T> - 表示迭代表达式的值。

  • index: number - 表示当前项的索引值。

  • first: boolean - 若当前项是可迭代对象的第一项,则返回 true。

  • last: boolean - 若当前项是可迭代对象的最后一项,则返回 true。

  • even: boolean - 若当前项的索引值是偶数,则返回 true。

  • odd: boolean - 若当前项的索引值是奇数,则返回 true。

Change Propagation

当可迭代对象的值改变时,NgForOf 对 DOM 会进行相应的更改:

  • 当新增某一项,对应的模板实例将会被添加到 DOM

  • 当移除某一项,对应的模板实例将会从 DOM 中移除

  • 当对可迭代对象每一项进行重新排序,它们各自的模板将在 DOM 中重新排序

  • 否则,页面中的 DOM 元素将保持不变。

Angular 使用对象标识来跟踪可迭代对象中,每一项的插入和删除,并在 DOM 中做出相应的变化。但使用对象标识有一个问题,假设我们通过服务端获取可迭代对象,当重新调用服务端接口获取新数据时,尽管服务端返回的数据没有变化,但它将产生一个新的对象。此时,Angular 将完全销毁可迭代对象相关的 DOM 元素,然后重新创建对应的 DOM 元素。这是一个很昂贵 (影响性能) 的操作,如果可能的话应该尽量避免。

因此,Angular 提供了 trackBy 选项,让我们能够自定义跟踪算法。 trackBy 选项需绑定到一个包含 indexitem 两个参数的函数对象。若设定了 trackBy 选项,Angular 将基于函数的返回值来跟踪变化。

IterableDiffers

用于跟踪可迭代对象变化差异。

TrackByFunction

用于定义 trackBy 绑定函数的类型:

  1. // packages/core/src/change_detection/differs/iterable_differs.ts
  2. export interface TrackByFunction<T> { (index: number, item: T): any; }

SimpleChanges

用于表示变化对象,对象的 keys 是变化的属性名,而对应的属性值是 SimpleChange 对象。

  1. // packages/core/src/metadata/lifecycle_hooks.ts
  2. export interface SimpleChanges { [propName: string]: SimpleChange; }

SimpleChange

用于表示从旧值到新值的基本变化。

  1. // packages/core/src/change_detection/change_detection_util.ts
  2. export class SimpleChange {
  3. constructor(
  4. public previousValue: any,
  5. public currentValue: any,
  6. public firstChange: boolean) {}
  7.  
  8. // 验证是否是首次变化
  9. isFirstChange(): boolean { return this.firstChange; }
  10. }

NgForOf 源码分析

NgForOf 指令定义

  1. @Directive({
  2. selector: '[ngFor][ngForOf]'
  3. })

NgForOf 类私有属性及构造函数

  1. // packages/common/src/directives/ng_for_of.ts
  2. export class NgForOf<T> implements DoCheck, OnChanges {
  3. private _differ: IterableDiffer<T>|null = null;
  4. private _trackByFn: TrackByFunction<T>;
  5.  
  6. constructor(
  7. private _viewContainer: ViewContainerRef,
  8. private _template: TemplateRef<NgForOfContext<T>>,
  9. private _differs: IterableDiffers) {}
  10. }

NgForOf 类输入属性

  1. // <ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
  2. @Input() ngForOf: NgIterable<T>; // 表示ngForOf属性,绑定的可迭代对象
  3. @Input()
  4. set ngForTrackBy(fn: TrackByFunction<T>) {
  5. // 在开发模式下,若ngForTrackBy属性绑定的对象不是函数类型,则提示用户。
  6. if (isDevMode() && fn != null && typeof fn !== 'function') {
  7. // TODO(vicb): use a log service once there is a public one available
  8. if (<any>console && <any>console.warn) {
  9. console.warn(
  10. `trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
  11. `See https://angular.io/docs/ts/latest/api/common/index/NgFor-
  12. directive.html#!#change-propagation for more information.`);
  13. }
  14. }
  15. this._trackByFn = fn;
  16. }
  17.  
  18. @Input()
  19. set ngForTemplate(value: TemplateRef<NgForOfContext<T>>) { // 表示ngFor对应的模板对象
  20. if (value) {
  21. this._template = value;
  22. }
  23. }

NgForOf 指令生命周期

  1. export class NgForOf<T> implements DoCheck, OnChanges {
  2. // 当输入属性发生变化,获取ngForOf绑定的可迭代对象的当前值,若_differ对象未创建,则基于ngForTrackBy
  3. // 函数创建IterableDiffer对象。
  4. ngOnChanges(changes: SimpleChanges): void {
  5. if ('ngForOf' in changes) {
  6. // React on ngForOf changes only once all inputs have been initialized
  7. const value = changes['ngForOf'].currentValue;
  8. if (!this._differ && value) {
  9. try {
  10. this._differ = this._differs.find(value).create(this.ngForTrackBy);
  11. } catch (e) {
  12. throw new Error(
  13. `Cannot find a differ supporting object '${value}' of type
  14. '${getTypeNameForDebugging(value)}'. NgFor only supports binding to
  15. Iterables such as Arrays.`);
  16. }
  17. }
  18. }
  19. }
  20.  
  21. // 调用IterableDiffer对象的diff()方法,计算可迭代对象变化的差异值,若发生变化则响应对应的变化。
  22. ngDoCheck(): void {
  23. if (this._differ) {
  24. const changes = this._differ.diff(this.ngForOf);
  25. if (changes) this._applyChanges(changes);
  26. }
  27. }
  28. }

通过源码我们发现在 ngDoCheck() 方法中,会调用 IterableDiffer 对象的 diff() 方法计算变化差异。该方法返回 IterableChanges 对象。现在我们来分析一下 IterableChanges 对象。

IterableChanges

IterableChanges 对象用于表示从上次调用 diff() 方法后,可迭代对象发生的变化。IterableChanges 接口定义如下:

  1. // packages/core/src/change_detection/differs/iterable_differs.ts
  2. export interface IterableChanges<V> {
  3. /** 迭代所有变化的项,IterableChangeRecord将包含每一项的变化信息 */
  4. forEachItem(fn: (record: IterableChangeRecord<V>) => void): void;
  5.  
  6. /** 对原始的可迭代对象应用执行对应的操作,从而产生新的可迭代对象 */
  7. forEachOperation(fn: (record: IterableChangeRecord<V>,
  8. previousIndex: number, currentIndex: number) => void): void;
  9.  
  10. /** 迭代原始Iterable的顺序的变化,显示原始项目移动的位置。*/
  11. forEachPreviousItem(fn: (record: IterableChangeRecord<V>) => void): void;
  12.  
  13. /** 迭代所有新增的项 */
  14. forEachAddedItem(fn: (record: IterableChangeRecord<V>) => void): void;
  15.  
  16. /** 迭代已移动的项 */
  17. forEachMovedItem(fn: (record: IterableChangeRecord<V>) => void): void;
  18.  
  19. /** 迭代已移除的项 */
  20. forEachRemovedItem(fn: (record: IterableChangeRecord<V>) => void): void;
  21.  
  22. /** 迭代所有基于trackByFn函数标识的变化项 */
  23. forEachIdentityChange(fn: (record: IterableChangeRecord<V>) => void): void;
  24. }

我们注意到每个迭代函数 fn 的输入参数类型是 IterableChangeRecord 对象。IterableChangeRecord 接口定义如下:

  1. // packages/core/src/change_detection/differs/iterable_differs.ts
  2. export interface IterableChangeRecord<V> {
  3. /** Current index of the item in `Iterable` or null if removed. */
  4. readonly currentIndex: number|null;
  5.  
  6. /** Previous index of the item in `Iterable` or null if added. */
  7. readonly previousIndex: number|null;
  8.  
  9. /** The item. */
  10. readonly item: V;
  11.  
  12. /** Track by identity as computed by the `trackByFn`. */
  13. readonly trackById: any;
  14. }

分析完 diff() 方法返回IterableChanges 对象,接下来我们来重点分析一下 _applyChanges 方法。

NgForOf 类私有方法

在介绍 NgForOf 类私有方法前,我们需要先介绍一下 RecordViewTuple 类,该类用于记录视图的变化。 RecordViewTuple 类的定义如下:

  1. class RecordViewTuple<T> {
  2. constructor(public record: any,
  3. public view: EmbeddedViewRef<NgForOfContext<T>>) {}
  4. }

介绍完 RecordViewTuple 类,我们马上来看一下 NgForOf 类中的私有方法:

  1. private _applyChanges(changes: IterableChanges<T>) {
  2. const insertTuples: RecordViewTuple<T>[] = [];
  3. // 基于IterableChanges对象,执行视图更新操作:如新增、删除或移动操作。
  4. changes.forEachOperation(
  5. (item: IterableChangeRecord<any>,
  6. adjustedPreviousIndex: number,
  7. currentIndex: number) => {
  8. // 对于新增的项,previousIndex的值为null
  9. if (item.previousIndex == null) {
  10. /**
  11. * export class NgForOfContext<T> {
  12. * constructor(
  13. * public $implicit: T,
  14. * public ngForOf: NgIterable<T>,
  15. * public index: number,
  16. * public count: number) {}
  17. * }
  18. */
  19. // 基于TemplateRef对象及NgForOfContext上下文创建内嵌视图
  20. const view = this._viewContainer.createEmbeddedView(
  21. this._template, new NgForOfContext<T>(null !, this.ngForOf, -, -),
  22. currentIndex);
  23. const tuple = new RecordViewTuple<T>(item, view);
  24. insertTuples.push(tuple);
  25. } else if (currentIndex == null) { // 对于已移除的项,currentIndex的值为null
  26. // 根据之前的索引值,在视图容器中移除对应的视图。
  27. this._viewContainer.remove(adjustedPreviousIndex);
  28. } else {
  29. // 执行视图的移动操作:先根据之前索引值获取对应的视图对象,然后将该视图移动到currentIndex
  30. // 指定的位置上。同时创建一个新的RecordViewTuple对象,用于记录该变化。
  31. const view = this._viewContainer.get(adjustedPreviousIndex) !;
  32. this._viewContainer.move(view, currentIndex);
  33. const tuple = new RecordViewTuple(item,
  34. <EmbeddedViewRef<NgForOfContext<T>>>view);
  35. insertTuples.push(tuple);
  36. }
  37. });
  38.  
  39. // 遍历视图变化记录数组(记录视图新增与移动操作),更新每一项中EmbeddedViewRef对象,context属性对应
  40. // 的上下文对象中$implicit属性的值为新的值。
  41. for (let i = ; i < insertTuples.length; i++) {
  42. this._perViewChange(insertTuples[i].view, insertTuples[i].record);
  43. }
  44.  
  45. // 遍历视图容器中的视图,设置视图上下文对象中的`index`和`count`的值。
  46. for (let i = , ilen = this._viewContainer.length; i < ilen; i++) {
  47. const viewRef = <EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(i);
  48. viewRef.context.index = i;
  49. viewRef.context.count = ilen;
  50. }
  51.  
  52. // 迭代所有基于trackByFn函数标识的变化项,更新每一项中EmbeddedViewRef对象,context属性对应
  53. // 的上下文对象中$implicit属性的值为新的值。
  54. changes.forEachIdentityChange((record: any) => {
  55. const viewRef =
  56. <EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(record.currentIndex);
  57. viewRef.context.$implicit = record.item;
  58. });
  59. }
  60.  
  61. private _perViewChange(
  62. view: EmbeddedViewRef<NgForOfContext<T>>,
  63. record: IterableChangeRecord<any>) {
  64. view.context.$implicit = record.item;
  65. }

NgForOf 指令的源码已经分析完了,该指令的核心就是如何高效的跟踪可迭代对象的变化,然后尽可能复用已有的 DOM 元素,来提高应用的性能。后面如果有时间的话,会整理专门的文章来分析 IterableDiffer 对象 diff() 算法具体实现。

在调用 ViewContainerRef 对象的 createEmbeddedView() 方法创建视图对象时,除了指定 TemplateRef 对象,我们还可以设置 TemplateRef 对象关联的上下文对象及视图的插入位置。其中上下文对象,用于作为解析模板绑定表达式的上下文。最后我们再来回顾一下以下语法,是不是感觉清晰很多。

  1. <ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
  2. <li>...</li>
  3. </ng-template>
  4. <!--等价于-->
  5. <ng-template ngFor let-item="$implicit" [ngForOf]="items" let-i="index"
  6. [ngForTrackBy]="trackByFn">
  7. <li>...</li>
  8. </ng-template>

AngularX 指令(ngForof)(转载)的更多相关文章

  1. Linux常用指令【转载】

    [收藏]Linux常用指令[转载] $ 命令行提示符 粗体表示命令 斜体表示参数 filename, file1, file2 都是文件名.有时文件名有后缀,比如file.zip command 命令 ...

  2. Linux常见操作指令(转载)

    Linux,免费开源,多用户多任务系统.基于Linux有多个版本的衍生.RedHat.Ubuntu.Debian 安装VMware或VirtualBox虚拟机.具体安装步骤,找百度. 再安装Ubunt ...

  3. SQL-.db 数据库查看常用指令(转载)

    一下内容转载自http://blog.sina.com.cn/s/blog_74dfa9f401017s69.html 简介sqlite3一款主要用于嵌入式的轻量级数据库,本文旨在为熟悉sqlite3 ...

  4. 在SQL Server中用好模糊查询指令LIKE (转载)

    like在sql中的使用:在SQL Server中用好模糊查询指令LIKE:查询是SQL Server中重要的功能,而在查询中将Like用上,可以搜索到一些意想不到的结果和效果,like的神奇 一.一 ...

  5. Appium+python自动化11-adb必知必会的几个指令【转载】

    前言 学android测试,adb是必学的,有几个常用的指令需要熟练掌握 一.检查设备 1.如何检查手机(或模拟器)是连上电脑的,在cmd输入: >adb devices

  6. ARM汇编中ldr伪指令和ldr指令(转载)

    转自:http://blog.csdn.net/ce123_zhouwei/article/details/7182756 ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成, ...

  7. samba - linux客户端访问samba服务器的指令(转载)

    转自:http://linux.sheup.com/linux/linux5303.htm linux客户端访问samba服务器的指令2004-04-23 15:18 pm来自:Linux文档现载:W ...

  8. 【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)

    作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42408137 转载请著名出处 本博客相关文档下载 :  -- AR ...

  9. JVM 内存初学 (堆(heap)、栈(stack)和方法区(method) )(转载)

    想想面试的时候很多会问jvm这方面的问题虽然还是菜鸟不太能用到现在但是还是了解一下, 找资料的时候看见个大佬写的很好转载到这方便以后自己复习和给大佬做宣传 以下为大佬的博客原文: 这两天看了一下深入浅 ...

随机推荐

  1. C# 操作FTP

    操作FTP管理类: using System; using System.Collections.Generic; using System.Text; using System.Net; using ...

  2. 模仿某旅行网站 纯css实现背景放大效果

    基本功能是鼠标移动到图片上,对应宽度变宽.其中布局和基本样式直接copy官网,功能部分是自己瞎鼓捣实现的. 直接上代码: HTML部分 <div class="fold_wrap&qu ...

  3. webstorm的安装、激活码、更换主题颜色的修改、汉化

    一.安装 1.解压webstorm11zh.rar,双击.exe文件,下一步安装,在安装结束前会提示输入激活码,这个从网上随便找一个可用的即可. 二.更换主题颜色: 1.先从网上找一个喜欢的主题颜色, ...

  4. 前端要不要学数据结构&算法

    我们都知道前端开发工程师更多偏向 DOM 渲染和 DOM 交互操作,随之 Node 的推广前端工程师也可以完成服务端开发.对于服务端开发而言大家都觉得数据结构和算法是基础,非学不可.所以正在进行 No ...

  5. 利用python 模块读取csv文件信息

    还有一个比较简单的方法 # -*- coding=utf-8 -*- import pandas as pddf = pd.read_csv("20170320094630.csv" ...

  6. 黑客落网记:FBI如何抓捕Anonymous核心成员

    腾讯科技讯 美国新闻网站Daily Dot近日撰文,通过他们掌握的资料和实地采访,还原了Anonymous核心成员被捕的经过. 以下为文章全文: 哈蒙德被捕前夜 2012年3月,一个周六的下午,天气异 ...

  7. Oracle Procedure记录

    1.定义 所谓存储过程(Procedure),就是一组用于完成特定数据库功能的SQL语句集,该SQL语句集经过 编译后存储在数据库系统中.在使用时候,用户通过指定已经定义的存储过程名字并给出相应的存储 ...

  8. Ubuntu下配置Nginx+PHP

    1.安装Nginxapt-get install nginx 2.启动Nginxservice nginx start 3.访问服务器IP 如果看到“Welcome to nginx!”说明安装好了. ...

  9. bzoj 1087 状压dp

    1087: [SCOI2005]互不侵犯King Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4130  Solved: 2390[Submit][ ...

  10. SSH集成log4j日志环境

    第一步:在web.xml初始化log4j <context-param> <param-name>contextConfigLocation</param-name> ...