详解Angular 4.x NgIf 的用法

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()

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

(0)

相关推荐

  • Angular2内置指令NgFor和NgIf详解

    在这一章节中,我们来学习如何使用Angular2来展示数据,以及如何使用它的内置指令NgFor和NgIf 首先要确保你有一个可以运行起来的Angular2的样例程序,最好就是我们上一章节中完成的那个QuickStart小项目或者你自己根据官网上面的步骤完成的QuickStart小项目,因为我们的讲解都是在那个基础上来进行的;然后让我们开始下面的快乐旅程吧. 因为我们的这一系列的文章都是使用的TypeScript所以在看下面的内容之前你最好可以看一下TypeScript或者ES6的类,它们的网址分

  • 详解Angular 4.x NgIf 的用法

    NgIf 指令作用 ngIf 指令用于根据表达式的值,在指定位置渲染 then 或 else 模板的内容. then 模板除非绑定到不同的值,否则默认是 ngIf 指令关联的内联模板. else 模板除非绑定对应的值,否则默认是 null. NgIf 指令语法 简单形式 <!--语法糖--> <div *ngIf="condition">...</div> <!--Angular 2.x中使用template--> <ng-temp

  • 详解Angular结构型指令模块和样式

    一,结构型指令 *是一个语法糖,<a *ngIf="user.login">退出</a>相当于 <ng-template [ngIf]="user.login"> <a>退出</a> </ng-template> 避免了写ng-template. <ng-template [ngIf]="item.reminder"> <mat-icon > alar

  • 详解Angular中$cacheFactory缓存的使用

    最近在学习使用angular,慢慢从jquery ui转型到用ng开发,发现了很多不同点,继续学习吧: 首先创建一个服务,以便在项目中的controller中引用,服务有几种存在形式,factory();service();constant();value();provider();其中provider是最基础的,其他服务都是基于这个写的,具体区别这里就不展开了,大家可以看看源码:服务是各个controller之间通话的重要形式,在实际项目中会用的很多,下面是代码: angular.module

  • 实例详解Android Selector和Shape的用法

    shape和selector是Android UI设计中经常用到的,比如我们要自定义一个圆角Button,点击Button有些效果的变化,就要用到shape和selector.可以这样说,shape和selector在美化控件中的作用是至关重要的. 1:Selector drawable的item中可以有以下属性: android:drawable="@[package:]drawable/drawable_resource" android:state_pressed=["

  • 详解python statistics模块及函数用法

    本节介绍 Python 中的另一个常用模块 -- statistics模块,该模块提供了用于计算数字数据的数理统计量的函数.它包含了很多函数,具体如下表: 名称 描述 mean() 数据的算术平均数("平均数") harmonic_mean() 数据的调和均值 median() 数据的中位数(中间值) median_low() 数据的低中位数 median_high() 数据的高中位数 median_grouped() 分组数据的中位数,即第50个百分点 mode() 离散的或标称的数

  • 详解Android studio 动态fragment的用法

    fragment的使用时Android的基础,它有两种用法,第一个就是静态的fragment.第二个则是动态的fragment. 静态fragment直接在layout创建你想要的fragment的XML的文件,然后在你的Java包里面创建对应fragment的class文件 布局代码如下所示 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http:

  • 详解Angular父子组件通讯

    概述 Angular组件间通讯 组件树,1号是根组件AppComponent. 组件之间松耦合,组件之间知道的越少越好. 组件4里面点击按钮,触发组件5的初始化逻辑. 传统做法:在按钮4的点击事件里调用组件5的方法.紧密耦合. Angular:在组件4根本不知道组件5存在的情况下实现. 使用松耦合的方式在组件之间传递数据开发出高重用性的组件. 使用输入输出属性在父子关系的组件之间传递数据. 一.输入输出属性概述 组件设计成黑盒模型,用输入属性声明从外部世界接收什么东西.不需要知道这些东西从哪里来

  • 详解Angular路由动画及高阶动画函数

    一.路由动画 路由动画需要在host元数据中指定触发器.动画注意不要过多,否则适得其反. 内容优先,引导用户去注意到某个内容.动画只是辅助手段. 在router.animate.ts中定义一个进场动画,一个离场动画. 因为进场动画和离场动画用的特别频繁,有一个别名叫:enter和:leave. import { trigger, state, transition, style, animate} from '@angular/animations'; export const slideToR

  • 详解Angular动态组件

    使用场景 我们先明确下动态组件的使用场景,在代码运行时要动态加载组件,换成普通人话,代码需要根据具体情况(比如用户的操作,向后台请求结果)确定在某些地方加载某些组件,这些组件不是静态的(不是固定的). 官网的举例就是,构建动态广告条,广告组件不断会推出新的,再用只支持静态组件结构的模板显然是不现实的. 再举一个常见的例子,动态弹出框,弹出的组件是不确定的.不断更新的,这里那里弹出个购买框,那那那又需要弹出样式选择框,静态组件结构模板是不能满足群众日渐增长的需求. 怎么实现 然后我们来找个把手,看

  • 详解Angular组件之投影

    概述 运行时动态改变组件模版的内容.没路由那么复杂,只是一段html,没有业务逻辑. ngContent指令将父组件模版上的任意片段投影到子组件上. 一.简单例子 1.子组件中使用<ng-content>指令来标记投影点 <div class="wrapper"> <h2>我是子组件</h2> <div>这个div定义在子组件中</div> <ng-content></ng-content>

随机推荐