欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Angular 4.x NgIf 的用法

程序员文章站 2022-03-20 15:08:29
ngif 指令作用 ngif 指令用于根据表达式的值,在指定位置渲染 then 或 else 模板的内容。 then 模板除非绑定到不同的值,否则默认是 n...

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

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