详解Angular 4.x NgIf 的用法
ngif 指令作用
ngif 指令用于根据表达式的值,在指定位置渲染 then 或 else 模板的内容。
- then 模板除非绑定到不同的值,否则默认是 ngif 指令关联的内联模板。
- 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()
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。