详解Angular 4.x NgIf 的用法


Posted in Javascript onMay 22, 2017

NgIf 指令作用

ngIf 指令用于根据表达式的值,在指定位置渲染 then 或 else 模板的内容。

  1. then 模板除非绑定到不同的值,否则默认是 ngIf 指令关联的内联模板。
  2. else 模板除非绑定对应的值,否则默认是 null。

NgIf 指令语法

简单形式

<!--语法糖-->
<div *ngIf="condition">...</div>
<!--Angular 2.x中使用template-->
<ng-template [ngIf]="condition"><div>...</div></ng-template>

使用else块

<div *ngIf="condition; else elseBlock">...</div>
<ng-template #elseBlock>...</ng-template>

使用then和else块

<div *ngIf="condition; then thenBlock else elseBlock"></div>
<ng-template #thenBlock>...</ng-template>
<ng-template #elseBlock>...</ng-template>

使用as语法

<div *ngIf="condition as value; else elseBlock">{{value}}</div>
<ng-template #elseBlock>...</ng-template>

NgIf 使用示例

@Component({
 selector: 'ng-if-then-else',
 template: `
  <button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
  <button (click)="switchPrimary()">Switch Primary</button>
    show = {{show}}
  <br>
  <div *ngIf="show; then thenBlock; else elseBlock">this is ignored</div>
  <ng-template #primaryBlock>Primary text to show</ng-template>
  <ng-template #secondaryBlock>Secondary text to show</ng-template>
  <ng-template #elseBlock>Alternate text while primary text is hidden</ng-template>
 `
})
class NgIfThenElse implements OnInit {
 thenBlock: TemplateRef<any> = null;
 show: boolean = true;
 
 @ViewChild('primaryBlock')
 primaryBlock: TemplateRef<any> = null;
 @ViewChild('secondaryBlock')
 secondaryBlock: TemplateRef<any> = null;
 
 switchPrimary() {
  this.thenBlock = this.thenBlock === this.primaryBlock ? 
   this.secondaryBlock : this.primaryBlock;
 }
 
 ngOnInit() { 
   this.thenBlock = this.primaryBlock;
 }
}

基础知识

TemplateRef
TemplateRef 实例用于表示模板对象,TemplateRef 抽象类的定义如下:

// angular\packages\core\src\linker\template_ref.ts
export abstract class TemplateRef<C> {
 abstract get elementRef(): ElementRef;
 abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
}

ViewContainerRef

ViewContainerRef 实例提供了 createEmbeddedView() 方法,该方法接收 TemplateRef 对象作为参数,并将模板中的内容作为容器 (comment 元素) 的兄弟元素,插入到页面中。

NgIfContext

NgIfContext 实例用于表示 NgIf 上下文。

// angular\packages\common\src\directives\ng_if.ts
export class NgIfContext {
 public $implicit: any = null;
 public ngIf: any = null;
}

NgIf 源码分析

NgIf 指令定义

@Directive({
  selector: '[ngIf]' // 属性选择器 - <ng-template [ngIf]="condition">
})

NgIf 类私有属性及构造函数

export class NgIf {
 // 创建NgIfContext上下文
 private _context: NgIfContext = new NgIfContext();
 // 表示then模板对象
 private _thenTemplateRef: TemplateRef<NgIfContext>|null = null;
 // 表示else模板对象
 private _elseTemplateRef: TemplateRef<NgIfContext>|null = null;

 // 表示根据then模板创建的EmbeddedViewRef视图
 private _thenViewRef: EmbeddedViewRef<NgIfContext>|null = null;
 // 表示根据else模板创建的EmbeddedViewRef视图
 private _elseViewRef: EmbeddedViewRef<NgIfContext>|null = null;

 constructor(
  private _viewContainer: ViewContainerRef, 
  templateRef: TemplateRef<NgIfContext>) {
   this._thenTemplateRef = templateRef; // then模板的默认值为ngIf指令关联的内联模板
 }
}

NgIf 类输入属性

@Input()
set ngIf(condition: any) {
  this._context.$implicit = this._context.ngIf = condition;
  this._updateView(); // 更新视图
}

@Input()
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
  this._thenTemplateRef = templateRef;
  this._thenViewRef = null; // 清除之前创建的视图
  this._updateView();
}

@Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
  this._elseTemplateRef = templateRef;
  this._elseViewRef = null; // 清除之前创建的视图
  this._updateView();
}

_updateView() 私有方法

// 更新视图
private _updateView() {
 // this._context.$implicit = this._context.ngIf = condition
 // 若condition表达式的值为truthy
 if (this._context.$implicit) {
 // 若_thenViewRef为null且_thenTemplateRef存在,则创建_thenViewRef内嵌视图
   if (!this._thenViewRef) {
    this._viewContainer.clear();
    this._elseViewRef = null;
    if (this._thenTemplateRef) {
     this._thenViewRef =
       this._viewContainer.createEmbeddedView(this._thenTemplateRef,
        this._context);
    }
   }
  } else { // condition表达式的值为falsy
   // 若_elseViewRef为null且_elseTemplateRef存在,则创建_elseViewRef内嵌视图
   if (!this._elseViewRef) {
    this._viewContainer.clear();
    this._thenViewRef = null;
    if (this._elseTemplateRef) {
     this._elseViewRef =
       this._viewContainer.createEmbeddedView(this._elseTemplateRef, 
        this._context);
    }
   }
  }
}

ngIf 指令的源码相对比较简单,最核心的是 _updateView() 方法。而该方法中最重要的功能就是如何基于模板对象创建内嵌视图。接下来我们来分析一下 ViewContainerRef 对象的 createEmbeddedView() 方法。

ViewContainerRef - createEmbeddedView()

方法签名

// angular\packages\core\src\linker\view_container_ref.ts
export abstract class ViewContainerRef {
  /**
  * 基于TemplateRef对象创建Embedded View(内嵌视图),然后根据`index`指定的值,插入到容器中。 
  * 如果没有指定`index`的值,新创建的视图将作为容器中的最后一个视图插入。
  */ 
 abstract createEmbeddedView<C>(
   templateRef: TemplateRef<C>, 
   context?: C, index?: number):
   EmbeddedViewRef<C>;
}

方法实现

// angular\packages\core\src\view\refs.ts
class ViewContainerRef_ implements ViewContainerData {
  // ...
  createEmbeddedView<C>(
   templateRef: TemplateRef<C>, 
   context?: C, index?: number):
   EmbeddedViewRef<C> {
    // 调用TemplateRef对象createEmbeddedView()方法创建EmbeddedViewRef对象
    const viewRef = templateRef.createEmbeddedView(context || <any>{});
     // 根据指定的index值,插入到视图容器中
    this.insert(viewRef, index);
    return viewRef;
 }
}

// ViewContainerData接口继承于ViewContainerRef抽象类
export interface ViewContainerData extends ViewContainerRef {
 _embeddedViews: ViewData[];
}

export interface ViewData {
 def: ViewDefinition;
 root: RootData;
 renderer: Renderer2;
 parentNodeDef: NodeDef|null;
 parent: ViewData|null;
 viewContainerParent: ViewData|null;
 component: any;
 context: any;
 nodes: {[key: number]: NodeData};
 state: ViewState;
 oldValues: any[];
 disposables: DisposableFn[]|null;
}

通过观察 ViewContainerRef_ 类中的 createEmbeddedView() 方法,我们发现该方法内部是调用 TemplateRef 对象的 createEmbeddedView() 方法来创建内嵌视图。因此接下来我们再来分析一下 TemplateRef 对象的 createEmbeddedView() 方法。

TemplateRef - createEmbeddedView()

方法签名

// angular\packages\core\src\linker\template_ref.ts
export abstract class TemplateRef<C> {
 abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
}

方法实现

// angular\packages\core\src\view\refs.ts
class TemplateRef_ extends TemplateRef<any> implements TemplateData {
 // ...
 createEmbeddedView(context: any): EmbeddedViewRef<any> {
  return new ViewRef_(Services.createEmbeddedView(
    this._parentView, this._def, this._def.element !.template !, context));
 }
}

export interface TemplateData extends TemplateRef<any> {
 _projectedViews: ViewData[];
}

看完上面的源码,毫无疑问接下来我们要继续分析 Services 对象中的 createEmbeddedView() 方法。

Services - createEmbeddedView()

Services 对象定义

// angular\packages\core\src\view\types.ts
export const Services: Services = {
 setCurrentNode: undefined !,
 createRootView: undefined !,
 createEmbeddedView: undefined !,
 createComponentView: undefined !,
 createNgModuleRef: undefined !,
 overrideProvider: undefined !,
 clearProviderOverrides: undefined !,
 checkAndUpdateView: undefined !,
 checkNoChangesView: undefined !,
 destroyView: undefined !,
 resolveDep: undefined !,
 createDebugContext: undefined !,
 handleEvent: undefined !,
 updateDirectives: undefined !,
 updateRenderer: undefined !,
 dirtyParentQueries: undefined !,
};

Services 对象初始化

// angular\packages\core\src\view\services.ts
export function initServicesIfNeeded() {
 if (initialized) {
  return;
 }
 initialized = true;
 const services = isDevMode() ? createDebugServices() : createProdServices();
 Services.setCurrentNode = services.setCurrentNode;
 Services.createRootView = services.createRootView;
 Services.createEmbeddedView = services.createEmbeddedView;
 Services.createComponentView = services.createComponentView;
 Services.createNgModuleRef = services.createNgModuleRef;
 Services.overrideProvider = services.overrideProvider;
 Services.clearProviderOverrides = services.clearProviderOverrides;
 Services.checkAndUpdateView = services.checkAndUpdateView;
 Services.checkNoChangesView = services.checkNoChangesView;
 Services.destroyView = services.destroyView;
 Services.resolveDep = resolveDep;
 Services.createDebugContext = services.createDebugContext;
 Services.handleEvent = services.handleEvent;
 Services.updateDirectives = services.updateDirectives;
 Services.updateRenderer = services.updateRenderer;
 Services.dirtyParentQueries = dirtyParentQueries;
}

initServicesIfNeeded() 方法中,会根据当前所处的模式,创建不同的 Services 对象。接下来 我们直接来看一下 createProdServices() 方法:

function createProdServices() {
 return {
  setCurrentNode: () => {},
  createRootView: createProdRootView,
  createEmbeddedView: createEmbeddedView // 省略了其它方法
}

createEmbeddedView() 方法

// angular\packages\core\src\view\view.ts
export function createEmbeddedView(
  parent: ViewData, anchorDef: NodeDef, viewDef: ViewDefinition, context?: any): ViewData {
 // embedded views are seen as siblings to the anchor, so we need
 // to get the parent of the anchor and use it as parentIndex.
 // 创建ViewData对象
 const view = createView(parent.root, parent.renderer, parent, anchorDef, viewDef);
 // 初始化ViewData对象-设置component及context属性的值
 initView(view, parent.component, context);
 // 创建视图中的节点,即设置view.nodes数组的属性值
 // const nodes = view.nodes; for(...) { ...; nodes[i] = nodeData; }
 createViewNodes(view);
 return view;
}

此时发现如果完整分析所有的方法,会涉及太多的内容。源码分析就到此结束,有兴趣的读者请自行阅读源码哈(请各位读者见谅)。接下来我们来总结一下 createEmbeddedView() 方法调用流程:

ViewContainerRef_ -> createEmbeddedView()
  => TemplateRef_ -> createEmbeddedView()
  => Services -> createEmbeddedView()
   => Call createEmbeddedView()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js 无提示关闭浏览器页面的代码
Mar 09 Javascript
客户端限制只能上传jpg格式图片的js代码
Dec 09 Javascript
js实现简单的购物车有图有代码
May 26 Javascript
js创建一个input数组并绑定click事件的方法
Jun 12 Javascript
JS简单限制textarea内输入字符数量的方法
Oct 14 Javascript
jQuery基于muipicker实现仿ios时间选择
Feb 22 Javascript
走进javascript——不起眼的基础,值和分号
Feb 24 Javascript
vue组件定义,全局、局部组件,配合模板及动态组件功能示例
Mar 19 Javascript
wx-charts 微信小程序图表插件的具体使用
Aug 18 Javascript
使用Vue.js中的过滤器实现幂方求值的方法
Aug 27 Javascript
JavaScript中this函数使用实例解析
Feb 21 Javascript
Vue项目配置跨域访问和代理proxy设置方式
Sep 08 Javascript
JS实现无缝循环marquee滚动效果
May 22 #Javascript
jQuery表单验证之密码确认
May 22 #jQuery
JavaScript实现QQ聊天消息展示和评论提交功能
May 22 #Javascript
单行 JS 实现移动端金钱格式的输入规则
May 22 #Javascript
JavaScript表单验证实现代码
May 22 #Javascript
JQuery 封装 Ajax 常用方法(推荐)
May 21 #jQuery
原生JS实现N级菜单的代码
May 21 #Javascript
You might like
PHP中str_replace函数使用小结
2008/10/11 PHP
调整PHP的性能
2013/10/30 PHP
php上传图片生成缩略图(GD库)
2016/01/06 PHP
ThinkPHP中Common/common.php文件常用函数功能分析
2016/05/20 PHP
深入理解PHP的远程多会话调试
2017/09/21 PHP
PHP实现的最大正向匹配算法示例
2017/12/19 PHP
让你的PHP,APACHE,NGINX支持大文件上传
2021/03/09 PHP
用js实现手把手教你月入万刀(转贴)
2007/11/07 Javascript
JS中的eval 为什么加括号
2016/04/13 Javascript
基于js里调用函数时,函数名带括号和不带括号的区别
2016/07/28 Javascript
详解vue.js组件化开发实践
2016/12/14 Javascript
JavaScript获取ul中li个数的方法
2017/02/13 Javascript
JS设计模式之命令模式概念与用法分析
2018/02/06 Javascript
node实现登录图片验证码的示例代码
2018/04/20 Javascript
vue+axios新手实践实现登陆的示例代码
2018/06/06 Javascript
vue中promise的使用及异步请求数据的方法
2018/11/08 Javascript
详解vuejs2.0 select 动态绑定下拉框支持多选
2019/04/25 Javascript
Vue实现Layui的集成方法步骤
2020/04/10 Javascript
Python中捕捉详细异常信息的代码示例
2014/09/18 Python
简单介绍Python中的struct模块
2015/04/28 Python
Django学习笔记之Class-Based-View
2017/02/15 Python
python使用正则表达式替换匹配成功的组并输出替换的次数
2017/11/22 Python
对pandas中时间窗函数rolling的使用详解
2018/11/28 Python
python flask几分钟实现web服务的例子
2019/07/26 Python
django使用admin站点上传图片的实例
2019/07/28 Python
python实现BP神经网络回归预测模型
2019/08/09 Python
python通过对字典的排序,对json字段进行排序的实例
2020/02/27 Python
Tensorflow之梯度裁剪的实现示例
2020/03/08 Python
windows、linux下打包Python3程序详细方法
2020/03/17 Python
什么是python的函数体
2020/06/19 Python
兰蔻美国官网:Lancome美国
2017/04/25 全球购物
应用心理学个人的求职信
2013/12/08 职场文书
建筑工程毕业生自我鉴定
2014/01/14 职场文书
关于运动会的口号
2014/06/07 职场文书
redis 查看所有的key方式
2021/05/07 Redis
python numpy中multiply与*及matul 的区别说明
2021/05/26 Python