Angular 4.x NgIf

NgIf 指令做用

ngIf 指令用於根據表達式的值,在指定位置渲染 thenelse 模板的內容。html

  • then 模板除非綁定到不一樣的值,不然默認是 ngIf 指令關聯的內聯模板。node

  • else 模板除非綁定對應的值,不然默認是 null。typescript

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 抽象類的定義以下:segmentfault

// 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 上下文。ide

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

若想進一步瞭解 TemplateRefViewContainerRef 的相關內容,請參考如下文章:函數

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() 方法。code

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()
相關文章
相關標籤/搜索